k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/apiserver/apply/apply_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package apiserver 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "flag" 24 "fmt" 25 "net/http" 26 "reflect" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/google/go-cmp/cmp" 32 "github.com/stretchr/testify/require" 33 "sigs.k8s.io/yaml" 34 35 appsv1 "k8s.io/api/apps/v1" 36 v1 "k8s.io/api/core/v1" 37 apierrors "k8s.io/apimachinery/pkg/api/errors" 38 "k8s.io/apimachinery/pkg/api/meta" 39 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 40 "k8s.io/apimachinery/pkg/runtime" 41 "k8s.io/apimachinery/pkg/types" 42 "k8s.io/apimachinery/pkg/util/wait" 43 yamlutil "k8s.io/apimachinery/pkg/util/yaml" 44 clientset "k8s.io/client-go/kubernetes" 45 restclient "k8s.io/client-go/rest" 46 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 47 "k8s.io/kubernetes/test/integration/framework" 48 ) 49 50 func setup(t testing.TB) (clientset.Interface, kubeapiservertesting.TearDownFunc) { 51 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 52 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 53 54 config := restclient.CopyConfig(server.ClientConfig) 55 // There are some tests (in scale_test.go) that rely on the response to be returned in JSON. 56 // So we overwrite it here. 57 config.ContentType = runtime.ContentTypeJSON 58 clientSet, err := clientset.NewForConfig(config) 59 if err != nil { 60 t.Fatalf("Error in create clientset: %v", err) 61 } 62 return clientSet, server.TearDownFn 63 } 64 65 // TestApplyAlsoCreates makes sure that PATCH requests with the apply content type 66 // will create the object if it doesn't already exist 67 // TODO: make a set of test cases in an easy-to-consume place (separate package?) so it's easy to test in both integration and e2e. 68 func TestApplyAlsoCreates(t *testing.T) { 69 client, closeFn := setup(t) 70 defer closeFn() 71 72 testCases := []struct { 73 resource string 74 name string 75 body string 76 }{ 77 { 78 resource: "pods", 79 name: "test-pod", 80 body: `{ 81 "apiVersion": "v1", 82 "kind": "Pod", 83 "metadata": { 84 "name": "test-pod" 85 }, 86 "spec": { 87 "containers": [{ 88 "name": "test-container", 89 "image": "test-image" 90 }] 91 } 92 }`, 93 }, { 94 resource: "services", 95 name: "test-svc", 96 body: `{ 97 "apiVersion": "v1", 98 "kind": "Service", 99 "metadata": { 100 "name": "test-svc" 101 }, 102 "spec": { 103 "ports": [{ 104 "port": 8080, 105 "protocol": "UDP" 106 }] 107 } 108 }`, 109 }, 110 } 111 112 for _, tc := range testCases { 113 t.Run(tc.name, func(t *testing.T) { 114 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 115 Namespace("default"). 116 Resource(tc.resource). 117 Name(tc.name). 118 Param("fieldManager", "apply_test"). 119 Body([]byte(tc.body)). 120 Do(context.TODO()). 121 Get() 122 if err != nil { 123 t.Fatalf("Failed to create object using Apply patch: %v", err) 124 } 125 126 _, err = client.CoreV1().RESTClient().Get().Namespace("default").Resource(tc.resource).Name(tc.name).Do(context.TODO()).Get() 127 if err != nil { 128 t.Fatalf("Failed to retrieve object: %v", err) 129 } 130 131 // Test that we can re apply with a different field manager and don't get conflicts 132 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 133 Namespace("default"). 134 Resource(tc.resource). 135 Name(tc.name). 136 Param("fieldManager", "apply_test_2"). 137 Body([]byte(tc.body)). 138 Do(context.TODO()). 139 Get() 140 if err != nil { 141 t.Fatalf("Failed to re-apply object using Apply patch: %v", err) 142 } 143 }) 144 } 145 } 146 147 // TestNoOpUpdateSameResourceVersion makes sure that PUT requests which change nothing 148 // will not change the resource version (no write to etcd is done) 149 func TestNoOpUpdateSameResourceVersion(t *testing.T) { 150 client, closeFn := setup(t) 151 defer closeFn() 152 153 podName := "no-op" 154 podResource := "pods" 155 podBytes := []byte(`{ 156 "apiVersion": "v1", 157 "kind": "Pod", 158 "metadata": { 159 "name": "` + podName + `", 160 "labels": { 161 "a": "one", 162 "c": "two", 163 "b": "three" 164 } 165 }, 166 "spec": { 167 "containers": [{ 168 "name": "test-container-a", 169 "image": "test-image-one" 170 },{ 171 "name": "test-container-c", 172 "image": "test-image-two" 173 },{ 174 "name": "test-container-b", 175 "image": "test-image-three" 176 }] 177 } 178 }`) 179 180 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 181 Namespace("default"). 182 Param("fieldManager", "apply_test"). 183 Resource(podResource). 184 Name(podName). 185 Body(podBytes). 186 Do(context.TODO()). 187 Get() 188 if err != nil { 189 t.Fatalf("Failed to create object: %v", err) 190 } 191 192 // Sleep for one second to make sure that the times of each update operation is different. 193 time.Sleep(1 * time.Second) 194 195 createdObject, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource(podResource).Name(podName).Do(context.TODO()).Get() 196 if err != nil { 197 t.Fatalf("Failed to retrieve created object: %v", err) 198 } 199 200 createdAccessor, err := meta.Accessor(createdObject) 201 if err != nil { 202 t.Fatalf("Failed to get meta accessor for created object: %v", err) 203 } 204 205 createdBytes, err := json.MarshalIndent(createdObject, "\t", "\t") 206 if err != nil { 207 t.Fatalf("Failed to marshal created object: %v", err) 208 } 209 210 // Test that we can put the same object and don't change the RV 211 _, err = client.CoreV1().RESTClient().Put(). 212 Namespace("default"). 213 Resource(podResource). 214 Name(podName). 215 Body(createdBytes). 216 Do(context.TODO()). 217 Get() 218 if err != nil { 219 t.Fatalf("Failed to apply no-op update: %v", err) 220 } 221 222 updatedObject, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource(podResource).Name(podName).Do(context.TODO()).Get() 223 if err != nil { 224 t.Fatalf("Failed to retrieve updated object: %v", err) 225 } 226 227 updatedAccessor, err := meta.Accessor(updatedObject) 228 if err != nil { 229 t.Fatalf("Failed to get meta accessor for updated object: %v", err) 230 } 231 232 updatedBytes, err := json.MarshalIndent(updatedObject, "\t", "\t") 233 if err != nil { 234 t.Fatalf("Failed to marshal updated object: %v", err) 235 } 236 237 if createdAccessor.GetResourceVersion() != updatedAccessor.GetResourceVersion() { 238 t.Fatalf("Expected same resource version to be %v but got: %v\nold object:\n%v\nnew object:\n%v", 239 createdAccessor.GetResourceVersion(), 240 updatedAccessor.GetResourceVersion(), 241 string(createdBytes), 242 string(updatedBytes), 243 ) 244 } 245 } 246 247 func getRV(obj runtime.Object) (string, error) { 248 acc, err := meta.Accessor(obj) 249 if err != nil { 250 return "", err 251 } 252 return acc.GetResourceVersion(), nil 253 } 254 255 func TestNoopChangeCreationTime(t *testing.T) { 256 client, closeFn := setup(t) 257 defer closeFn() 258 259 ssBytes := []byte(`{ 260 "apiVersion": "v1", 261 "kind": "ConfigMap", 262 "metadata": { 263 "name": "myconfig", 264 "creationTimestamp": null, 265 "resourceVersion": null 266 }, 267 "data": { 268 "key": "value" 269 } 270 }`) 271 272 obj, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 273 Namespace("default"). 274 Param("fieldManager", "apply_test"). 275 Resource("configmaps"). 276 Name("myconfig"). 277 Body(ssBytes). 278 Do(context.TODO()). 279 Get() 280 if err != nil { 281 t.Fatalf("Failed to create object: %v", err) 282 } 283 284 require.NoError(t, err) 285 // Sleep for one second to make sure that the times of each update operation is different. 286 time.Sleep(1200 * time.Millisecond) 287 288 newObj, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 289 Namespace("default"). 290 Param("fieldManager", "apply_test"). 291 Resource("configmaps"). 292 Name("myconfig"). 293 Body(ssBytes). 294 Do(context.TODO()). 295 Get() 296 if err != nil { 297 t.Fatalf("Failed to create object: %v", err) 298 } 299 300 require.NoError(t, err) 301 require.Equal(t, obj, newObj) 302 } 303 304 // TestNoSemanticUpdateAppleSameResourceVersion makes sure that APPLY requests which makes no semantic changes 305 // will not change the resource version (no write to etcd is done) 306 // 307 // Some of the non-semantic changes are: 308 // - Applying an atomic struct that removes a default 309 // - Changing Quantity or other fields that are normalized 310 func TestNoSemanticUpdateApplySameResourceVersion(t *testing.T) { 311 client, closeFn := setup(t) 312 defer closeFn() 313 314 ssBytes := []byte(`{ 315 "apiVersion": "apps/v1", 316 "kind": "StatefulSet", 317 "metadata": { 318 "name": "nginx", 319 "labels": {"app": "nginx"} 320 }, 321 "spec": { 322 "serviceName": "nginx", 323 "selector": { "matchLabels": {"app": "nginx"}}, 324 "template": { 325 "metadata": { 326 "labels": {"app": "nginx"} 327 }, 328 "spec": { 329 "containers": [{ 330 "name": "nginx", 331 "image": "nginx", 332 "resources": { 333 "limits": {"memory": "2048Mi"} 334 } 335 }] 336 } 337 }, 338 "volumeClaimTemplates": [{ 339 "metadata": {"name": "nginx"}, 340 "spec": { 341 "accessModes": ["ReadWriteOnce"], 342 "resources": {"requests": {"storage": "1Gi"}} 343 } 344 }] 345 } 346 }`) 347 348 obj, err := client.AppsV1().RESTClient().Patch(types.ApplyPatchType). 349 Namespace("default"). 350 Param("fieldManager", "apply_test"). 351 Resource("statefulsets"). 352 Name("nginx"). 353 Body(ssBytes). 354 Do(context.TODO()). 355 Get() 356 if err != nil { 357 t.Fatalf("Failed to create object: %v", err) 358 } 359 360 rvCreated, err := getRV(obj) 361 if err != nil { 362 t.Fatalf("Failed to get RV: %v", err) 363 } 364 365 // Sleep for one second to make sure that the times of each update operation is different. 366 time.Sleep(1200 * time.Millisecond) 367 368 obj, err = client.AppsV1().RESTClient().Patch(types.ApplyPatchType). 369 Namespace("default"). 370 Param("fieldManager", "apply_test"). 371 Resource("statefulsets"). 372 Name("nginx"). 373 Body(ssBytes). 374 Do(context.TODO()). 375 Get() 376 if err != nil { 377 t.Fatalf("Failed to create object: %v", err) 378 } 379 rvApplied, err := getRV(obj) 380 if err != nil { 381 t.Fatalf("Failed to get RV: %v", err) 382 } 383 if rvApplied != rvCreated { 384 t.Fatal("ResourceVersion changed after apply") 385 } 386 } 387 388 // TestNoSemanticUpdateAppleSameResourceVersion makes sure that PUT requests which makes no semantic changes 389 // will not change the resource version (no write to etcd is done) 390 // 391 // Some of the non-semantic changes are: 392 // - Applying an atomic struct that removes a default 393 // - Changing Quantity or other fields that are normalized 394 func TestNoSemanticUpdatePutSameResourceVersion(t *testing.T) { 395 client, closeFn := setup(t) 396 defer closeFn() 397 398 ssBytes := []byte(`{ 399 "apiVersion": "apps/v1", 400 "kind": "StatefulSet", 401 "metadata": { 402 "name": "nginx", 403 "labels": {"app": "nginx"} 404 }, 405 "spec": { 406 "serviceName": "nginx", 407 "selector": { "matchLabels": {"app": "nginx"}}, 408 "template": { 409 "metadata": { 410 "labels": {"app": "nginx"} 411 }, 412 "spec": { 413 "containers": [{ 414 "name": "nginx", 415 "image": "nginx", 416 "resources": { 417 "limits": {"memory": "2048Mi"} 418 } 419 }] 420 } 421 }, 422 "volumeClaimTemplates": [{ 423 "metadata": {"name": "nginx"}, 424 "spec": { 425 "accessModes": ["ReadWriteOnce"], 426 "resources": { "requests": { "storage": "1Gi"}} 427 } 428 }] 429 } 430 }`) 431 432 obj, err := client.AppsV1().RESTClient().Post(). 433 Namespace("default"). 434 Param("fieldManager", "apply_test"). 435 Resource("statefulsets"). 436 Body(ssBytes). 437 Do(context.TODO()). 438 Get() 439 if err != nil { 440 t.Fatalf("Failed to create object: %v", err) 441 } 442 443 rvCreated, err := getRV(obj) 444 if err != nil { 445 t.Fatalf("Failed to get RV: %v", err) 446 } 447 448 // Sleep for one second to make sure that the times of each update operation is different. 449 time.Sleep(1200 * time.Millisecond) 450 451 obj, err = client.AppsV1().RESTClient().Put(). 452 Namespace("default"). 453 Param("fieldManager", "apply_test"). 454 Resource("statefulsets"). 455 Name("nginx"). 456 Body(ssBytes). 457 Do(context.TODO()). 458 Get() 459 if err != nil { 460 t.Fatalf("Failed to create object: %v", err) 461 } 462 rvApplied, err := getRV(obj) 463 if err != nil { 464 t.Fatalf("Failed to get RV: %v", err) 465 } 466 if rvApplied != rvCreated { 467 t.Fatal("ResourceVersion changed after similar PUT") 468 } 469 } 470 471 // TestCreateOnApplyFailsWithUID makes sure that PATCH requests with the apply content type 472 // will not create the object if it doesn't already exist and it specifies a UID 473 func TestCreateOnApplyFailsWithUID(t *testing.T) { 474 client, closeFn := setup(t) 475 defer closeFn() 476 477 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 478 Namespace("default"). 479 Resource("pods"). 480 Name("test-pod-uid"). 481 Param("fieldManager", "apply_test"). 482 Body([]byte(`{ 483 "apiVersion": "v1", 484 "kind": "Pod", 485 "metadata": { 486 "name": "test-pod-uid", 487 "uid": "88e00824-7f0e-11e8-94a1-c8d3ffb15800" 488 }, 489 "spec": { 490 "containers": [{ 491 "name": "test-container", 492 "image": "test-image" 493 }] 494 } 495 }`)). 496 Do(context.TODO()). 497 Get() 498 if !apierrors.IsConflict(err) { 499 t.Fatalf("Expected conflict error but got: %v", err) 500 } 501 } 502 503 func TestApplyUpdateApplyConflictForced(t *testing.T) { 504 client, closeFn := setup(t) 505 defer closeFn() 506 507 obj := []byte(`{ 508 "apiVersion": "apps/v1", 509 "kind": "Deployment", 510 "metadata": { 511 "name": "deployment", 512 "labels": {"app": "nginx"} 513 }, 514 "spec": { 515 "replicas": 3, 516 "selector": { 517 "matchLabels": { 518 "app": "nginx" 519 } 520 }, 521 "template": { 522 "metadata": { 523 "labels": { 524 "app": "nginx" 525 } 526 }, 527 "spec": { 528 "containers": [{ 529 "name": "nginx", 530 "image": "nginx:latest" 531 }] 532 } 533 } 534 } 535 }`) 536 537 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 538 AbsPath("/apis/apps/v1"). 539 Namespace("default"). 540 Resource("deployments"). 541 Name("deployment"). 542 Param("fieldManager", "apply_test"). 543 Body(obj).Do(context.TODO()).Get() 544 if err != nil { 545 t.Fatalf("Failed to create object using Apply patch: %v", err) 546 } 547 548 _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). 549 AbsPath("/apis/apps/v1"). 550 Namespace("default"). 551 Resource("deployments"). 552 Name("deployment"). 553 Body([]byte(`{"spec":{"replicas": 5}}`)).Do(context.TODO()).Get() 554 if err != nil { 555 t.Fatalf("Failed to patch object: %v", err) 556 } 557 558 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 559 AbsPath("/apis/apps/v1"). 560 Namespace("default"). 561 Resource("deployments"). 562 Name("deployment"). 563 Param("fieldManager", "apply_test"). 564 Body([]byte(obj)).Do(context.TODO()).Get() 565 if err == nil { 566 t.Fatalf("Expecting to get conflicts when applying object") 567 } 568 status, ok := err.(*apierrors.StatusError) 569 if !ok { 570 t.Fatalf("Expecting to get conflicts as API error") 571 } 572 if len(status.Status().Details.Causes) < 1 { 573 t.Fatalf("Expecting to get at least one conflict when applying object, got: %v", status.Status().Details.Causes) 574 } 575 576 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 577 AbsPath("/apis/apps/v1"). 578 Namespace("default"). 579 Resource("deployments"). 580 Name("deployment"). 581 Param("force", "true"). 582 Param("fieldManager", "apply_test"). 583 Body([]byte(obj)).Do(context.TODO()).Get() 584 if err != nil { 585 t.Fatalf("Failed to apply object with force: %v", err) 586 } 587 } 588 589 // TestApplyGroupsManySeparateUpdates tests that when many different managers update the same object, 590 // the number of managedFields entries will only grow to a certain size. 591 func TestApplyGroupsManySeparateUpdates(t *testing.T) { 592 client, closeFn := setup(t) 593 defer closeFn() 594 595 obj := []byte(`{ 596 "apiVersion": "admissionregistration.k8s.io/v1", 597 "kind": "ValidatingWebhookConfiguration", 598 "metadata": { 599 "name": "webhook", 600 "labels": {"applier":"true"}, 601 }, 602 }`) 603 604 object, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 605 AbsPath("/apis/admissionregistration.k8s.io/v1"). 606 Resource("validatingwebhookconfigurations"). 607 Name("webhook"). 608 Param("fieldManager", "apply_test"). 609 Body(obj).Do(context.TODO()).Get() 610 if err != nil { 611 t.Fatalf("Failed to create object using Apply patch: %v", err) 612 } 613 614 for i := 0; i < 20; i++ { 615 unique := fmt.Sprintf("updater%v", i) 616 object, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). 617 AbsPath("/apis/admissionregistration.k8s.io/v1"). 618 Resource("validatingwebhookconfigurations"). 619 Name("webhook"). 620 Param("fieldManager", unique). 621 Body([]byte(`{"metadata":{"labels":{"` + unique + `":"new"}}}`)).Do(context.TODO()).Get() 622 if err != nil { 623 t.Fatalf("Failed to patch object: %v", err) 624 } 625 } 626 627 accessor, err := meta.Accessor(object) 628 if err != nil { 629 t.Fatalf("Failed to get meta accessor: %v", err) 630 } 631 632 // Expect 11 entries, because the cap for update entries is 10, and 1 apply entry 633 if actual, expected := len(accessor.GetManagedFields()), 11; actual != expected { 634 if b, err := json.MarshalIndent(object, "\t", "\t"); err == nil { 635 t.Fatalf("Object expected to contain %v entries in managedFields, but got %v:\n%v", expected, actual, string(b)) 636 } else { 637 t.Fatalf("Object expected to contain %v entries in managedFields, but got %v: error marshalling object: %v", expected, actual, err) 638 } 639 } 640 641 // Expect the first entry to have the manager name "apply_test" 642 if actual, expected := accessor.GetManagedFields()[0].Manager, "apply_test"; actual != expected { 643 t.Fatalf("Expected first manager to be named %v but got %v", expected, actual) 644 } 645 646 // Expect the second entry to have the manager name "ancient-changes" 647 if actual, expected := accessor.GetManagedFields()[1].Manager, "ancient-changes"; actual != expected { 648 t.Fatalf("Expected first manager to be named %v but got %v", expected, actual) 649 } 650 } 651 652 // TestCreateVeryLargeObject tests that a very large object can be created without exceeding the size limit due to managedFields 653 func TestCreateVeryLargeObject(t *testing.T) { 654 client, closeFn := setup(t) 655 defer closeFn() 656 657 cfg := &v1.ConfigMap{ 658 ObjectMeta: metav1.ObjectMeta{ 659 Name: "large-create-test-cm", 660 Namespace: "default", 661 }, 662 Data: map[string]string{}, 663 } 664 665 for i := 0; i < 9999; i++ { 666 unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i) 667 cfg.Data[unique] = "A" 668 } 669 670 // Should be able to create an object near the object size limit. 671 if _, err := client.CoreV1().ConfigMaps(cfg.Namespace).Create(context.TODO(), cfg, metav1.CreateOptions{}); err != nil { 672 t.Errorf("unable to create large test configMap: %v", err) 673 } 674 675 // Applying to the same object should cause managedFields to go over the object size limit, and fail. 676 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 677 Namespace(cfg.Namespace). 678 Resource("configmaps"). 679 Name(cfg.Name). 680 Param("fieldManager", "apply_test"). 681 Body([]byte(`{ 682 "apiVersion": "v1", 683 "kind": "ConfigMap", 684 "metadata": { 685 "name": "large-create-test-cm", 686 "namespace": "default", 687 } 688 }`)). 689 Do(context.TODO()). 690 Get() 691 if err == nil { 692 t.Fatalf("expected to fail to update object using Apply patch, but succeeded") 693 } 694 } 695 696 // TestUpdateVeryLargeObject tests that a small object can be updated to be very large without exceeding the size limit due to managedFields 697 func TestUpdateVeryLargeObject(t *testing.T) { 698 client, closeFn := setup(t) 699 defer closeFn() 700 701 cfg := &v1.ConfigMap{ 702 ObjectMeta: metav1.ObjectMeta{ 703 Name: "large-update-test-cm", 704 Namespace: "default", 705 }, 706 Data: map[string]string{"k": "v"}, 707 } 708 709 // Create a small config map. 710 cfg, err := client.CoreV1().ConfigMaps(cfg.Namespace).Create(context.TODO(), cfg, metav1.CreateOptions{}) 711 if err != nil { 712 t.Errorf("unable to create configMap: %v", err) 713 } 714 715 // Should be able to update a small object to be near the object size limit. 716 var updateErr error 717 pollErr := wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { 718 updateCfg, err := client.CoreV1().ConfigMaps(cfg.Namespace).Get(context.TODO(), cfg.Name, metav1.GetOptions{}) 719 if err != nil { 720 return false, err 721 } 722 723 // Apply the large update, then attempt to push it to the apiserver. 724 for i := 0; i < 9999; i++ { 725 unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i) 726 updateCfg.Data[unique] = "A" 727 } 728 729 if _, err = client.CoreV1().ConfigMaps(cfg.Namespace).Update(context.TODO(), updateCfg, metav1.UpdateOptions{}); err == nil { 730 return true, nil 731 } 732 updateErr = err 733 return false, nil 734 }) 735 if pollErr == wait.ErrWaitTimeout { 736 t.Errorf("unable to update configMap: %v", updateErr) 737 } 738 739 // Applying to the same object should cause managedFields to go over the object size limit, and fail. 740 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 741 Namespace(cfg.Namespace). 742 Resource("configmaps"). 743 Name(cfg.Name). 744 Param("fieldManager", "apply_test"). 745 Body([]byte(`{ 746 "apiVersion": "v1", 747 "kind": "ConfigMap", 748 "metadata": { 749 "name": "large-update-test-cm", 750 "namespace": "default", 751 } 752 }`)). 753 Do(context.TODO()). 754 Get() 755 if err == nil { 756 t.Fatalf("expected to fail to update object using Apply patch, but succeeded") 757 } 758 } 759 760 // TestPatchVeryLargeObject tests that a small object can be patched to be very large without exceeding the size limit due to managedFields 761 func TestPatchVeryLargeObject(t *testing.T) { 762 client, closeFn := setup(t) 763 defer closeFn() 764 765 cfg := &v1.ConfigMap{ 766 ObjectMeta: metav1.ObjectMeta{ 767 Name: "large-patch-test-cm", 768 Namespace: "default", 769 }, 770 Data: map[string]string{"k": "v"}, 771 } 772 773 // Create a small config map. 774 if _, err := client.CoreV1().ConfigMaps(cfg.Namespace).Create(context.TODO(), cfg, metav1.CreateOptions{}); err != nil { 775 t.Errorf("unable to create configMap: %v", err) 776 } 777 778 patchString := `{"data":{"k":"v"` 779 for i := 0; i < 9999; i++ { 780 unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i) 781 patchString = fmt.Sprintf("%s,%q:%q", patchString, unique, "A") 782 } 783 patchString = fmt.Sprintf("%s}}", patchString) 784 785 // Should be able to update a small object to be near the object size limit. 786 _, err := client.CoreV1().RESTClient().Patch(types.MergePatchType). 787 AbsPath("/api/v1"). 788 Namespace(cfg.Namespace). 789 Resource("configmaps"). 790 Name(cfg.Name). 791 Body([]byte(patchString)).Do(context.TODO()).Get() 792 if err != nil { 793 t.Errorf("unable to patch configMap: %v", err) 794 } 795 796 // Applying to the same object should cause managedFields to go over the object size limit, and fail. 797 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 798 Namespace("default"). 799 Resource("configmaps"). 800 Name("large-patch-test-cm"). 801 Param("fieldManager", "apply_test"). 802 Body([]byte(`{ 803 "apiVersion": "v1", 804 "kind": "ConfigMap", 805 "metadata": { 806 "name": "large-patch-test-cm", 807 "namespace": "default", 808 } 809 }`)). 810 Do(context.TODO()). 811 Get() 812 if err == nil { 813 t.Fatalf("expected to fail to update object using Apply patch, but succeeded") 814 } 815 } 816 817 // TestApplyManagedFields makes sure that managedFields api does not change 818 func TestApplyManagedFields(t *testing.T) { 819 client, closeFn := setup(t) 820 defer closeFn() 821 822 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 823 Namespace("default"). 824 Resource("configmaps"). 825 Name("test-cm"). 826 Param("fieldManager", "apply_test"). 827 Body([]byte(`{ 828 "apiVersion": "v1", 829 "kind": "ConfigMap", 830 "metadata": { 831 "name": "test-cm", 832 "namespace": "default", 833 "labels": { 834 "test-label": "test" 835 } 836 }, 837 "data": { 838 "key": "value" 839 } 840 }`)). 841 Do(context.TODO()). 842 Get() 843 if err != nil { 844 t.Fatalf("Failed to create object using Apply patch: %v", err) 845 } 846 847 _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). 848 Namespace("default"). 849 Resource("configmaps"). 850 Name("test-cm"). 851 Param("fieldManager", "updater"). 852 Body([]byte(`{"data":{"new-key": "value"}}`)).Do(context.TODO()).Get() 853 if err != nil { 854 t.Fatalf("Failed to patch object: %v", err) 855 } 856 857 // Sleep for one second to make sure that the times of each update operation is different. 858 // This will let us check that update entries with the same manager name are grouped together, 859 // and that the most recent update time is recorded in the grouped entry. 860 time.Sleep(1 * time.Second) 861 862 _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). 863 Namespace("default"). 864 Resource("configmaps"). 865 Name("test-cm"). 866 Param("fieldManager", "updater"). 867 Body([]byte(`{"data":{"key": "new value"}}`)).Do(context.TODO()).Get() 868 if err != nil { 869 t.Fatalf("Failed to patch object: %v", err) 870 } 871 872 object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get() 873 if err != nil { 874 t.Fatalf("Failed to retrieve object: %v", err) 875 } 876 877 accessor, err := meta.Accessor(object) 878 if err != nil { 879 t.Fatalf("Failed to get meta accessor: %v", err) 880 } 881 882 actual, err := json.MarshalIndent(object, "\t", "\t") 883 if err != nil { 884 t.Fatalf("Failed to marshal object: %v", err) 885 } 886 887 expected := []byte(`{ 888 "metadata": { 889 "name": "test-cm", 890 "namespace": "default", 891 "uid": "` + string(accessor.GetUID()) + `", 892 "resourceVersion": "` + accessor.GetResourceVersion() + `", 893 "creationTimestamp": "` + accessor.GetCreationTimestamp().UTC().Format(time.RFC3339) + `", 894 "labels": { 895 "test-label": "test" 896 }, 897 "managedFields": [ 898 { 899 "manager": "apply_test", 900 "operation": "Apply", 901 "apiVersion": "v1", 902 "time": "` + accessor.GetManagedFields()[0].Time.UTC().Format(time.RFC3339) + `", 903 "fieldsType": "FieldsV1", 904 "fieldsV1": { 905 "f:metadata": { 906 "f:labels": { 907 "f:test-label": {} 908 } 909 } 910 } 911 }, 912 { 913 "manager": "updater", 914 "operation": "Update", 915 "apiVersion": "v1", 916 "time": "` + accessor.GetManagedFields()[1].Time.UTC().Format(time.RFC3339) + `", 917 "fieldsType": "FieldsV1", 918 "fieldsV1": { 919 "f:data": { 920 "f:key": {}, 921 "f:new-key": {} 922 } 923 } 924 } 925 ] 926 }, 927 "data": { 928 "key": "new value", 929 "new-key": "value" 930 } 931 }`) 932 933 if string(expected) != string(actual) { 934 t.Fatalf("Expected:\n%v\nGot:\n%v", string(expected), string(actual)) 935 } 936 937 if accessor.GetManagedFields()[0].Time.UTC().Format(time.RFC3339) == accessor.GetManagedFields()[1].Time.UTC().Format(time.RFC3339) { 938 t.Fatalf("Expected times to be different but got:\n%v", string(actual)) 939 } 940 } 941 942 // TestApplyRemovesEmptyManagedFields there are no empty managers in managedFields 943 func TestApplyRemovesEmptyManagedFields(t *testing.T) { 944 client, closeFn := setup(t) 945 defer closeFn() 946 947 obj := []byte(`{ 948 "apiVersion": "v1", 949 "kind": "ConfigMap", 950 "metadata": { 951 "name": "test-cm", 952 "namespace": "default" 953 } 954 }`) 955 956 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 957 Namespace("default"). 958 Resource("configmaps"). 959 Name("test-cm"). 960 Param("fieldManager", "apply_test"). 961 Body(obj). 962 Do(context.TODO()). 963 Get() 964 if err != nil { 965 t.Fatalf("Failed to create object using Apply patch: %v", err) 966 } 967 968 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 969 Namespace("default"). 970 Resource("configmaps"). 971 Name("test-cm"). 972 Param("fieldManager", "apply_test"). 973 Body(obj).Do(context.TODO()).Get() 974 if err != nil { 975 t.Fatalf("Failed to patch object: %v", err) 976 } 977 978 object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get() 979 if err != nil { 980 t.Fatalf("Failed to retrieve object: %v", err) 981 } 982 983 accessor, err := meta.Accessor(object) 984 if err != nil { 985 t.Fatalf("Failed to get meta accessor: %v", err) 986 } 987 988 if managed := accessor.GetManagedFields(); managed != nil { 989 t.Fatalf("Object contains unexpected managedFields: %v", managed) 990 } 991 } 992 993 func TestApplyRequiresFieldManager(t *testing.T) { 994 client, closeFn := setup(t) 995 defer closeFn() 996 997 obj := []byte(`{ 998 "apiVersion": "v1", 999 "kind": "ConfigMap", 1000 "metadata": { 1001 "name": "test-cm", 1002 "namespace": "default" 1003 } 1004 }`) 1005 1006 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1007 Namespace("default"). 1008 Resource("configmaps"). 1009 Name("test-cm"). 1010 Body(obj). 1011 Do(context.TODO()). 1012 Get() 1013 if err == nil { 1014 t.Fatalf("Apply should fail to create without fieldManager") 1015 } 1016 1017 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1018 Namespace("default"). 1019 Resource("configmaps"). 1020 Name("test-cm"). 1021 Param("fieldManager", "apply_test"). 1022 Body(obj). 1023 Do(context.TODO()). 1024 Get() 1025 if err != nil { 1026 t.Fatalf("Apply failed to create with fieldManager: %v", err) 1027 } 1028 } 1029 1030 // TestApplyRemoveContainerPort removes a container port from a deployment 1031 func TestApplyRemoveContainerPort(t *testing.T) { 1032 client, closeFn := setup(t) 1033 defer closeFn() 1034 1035 obj := []byte(`{ 1036 "apiVersion": "apps/v1", 1037 "kind": "Deployment", 1038 "metadata": { 1039 "name": "deployment", 1040 "labels": {"app": "nginx"} 1041 }, 1042 "spec": { 1043 "replicas": 3, 1044 "selector": { 1045 "matchLabels": { 1046 "app": "nginx" 1047 } 1048 }, 1049 "template": { 1050 "metadata": { 1051 "labels": { 1052 "app": "nginx" 1053 } 1054 }, 1055 "spec": { 1056 "containers": [{ 1057 "name": "nginx", 1058 "image": "nginx:latest", 1059 "ports": [{ 1060 "containerPort": 80, 1061 "protocol": "TCP" 1062 }] 1063 }] 1064 } 1065 } 1066 } 1067 }`) 1068 1069 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1070 AbsPath("/apis/apps/v1"). 1071 Namespace("default"). 1072 Resource("deployments"). 1073 Name("deployment"). 1074 Param("fieldManager", "apply_test"). 1075 Body(obj).Do(context.TODO()).Get() 1076 if err != nil { 1077 t.Fatalf("Failed to create object using Apply patch: %v", err) 1078 } 1079 1080 obj = []byte(`{ 1081 "apiVersion": "apps/v1", 1082 "kind": "Deployment", 1083 "metadata": { 1084 "name": "deployment", 1085 "labels": {"app": "nginx"} 1086 }, 1087 "spec": { 1088 "replicas": 3, 1089 "selector": { 1090 "matchLabels": { 1091 "app": "nginx" 1092 } 1093 }, 1094 "template": { 1095 "metadata": { 1096 "labels": { 1097 "app": "nginx" 1098 } 1099 }, 1100 "spec": { 1101 "containers": [{ 1102 "name": "nginx", 1103 "image": "nginx:latest" 1104 }] 1105 } 1106 } 1107 } 1108 }`) 1109 1110 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1111 AbsPath("/apis/apps/v1"). 1112 Namespace("default"). 1113 Resource("deployments"). 1114 Name("deployment"). 1115 Param("fieldManager", "apply_test"). 1116 Body(obj).Do(context.TODO()).Get() 1117 if err != nil { 1118 t.Fatalf("Failed to remove container port using Apply patch: %v", err) 1119 } 1120 1121 deployment, err := client.AppsV1().Deployments("default").Get(context.TODO(), "deployment", metav1.GetOptions{}) 1122 if err != nil { 1123 t.Fatalf("Failed to retrieve object: %v", err) 1124 } 1125 1126 if len(deployment.Spec.Template.Spec.Containers[0].Ports) > 0 { 1127 t.Fatalf("Expected no container ports but got: %v, object: \n%#v", deployment.Spec.Template.Spec.Containers[0].Ports, deployment) 1128 } 1129 } 1130 1131 // TestApplyFailsWithVersionMismatch ensures that a version mismatch between the 1132 // patch object and the live object will error 1133 func TestApplyFailsWithVersionMismatch(t *testing.T) { 1134 client, closeFn := setup(t) 1135 defer closeFn() 1136 1137 obj := []byte(`{ 1138 "apiVersion": "apps/v1", 1139 "kind": "Deployment", 1140 "metadata": { 1141 "name": "deployment", 1142 "labels": {"app": "nginx"} 1143 }, 1144 "spec": { 1145 "replicas": 3, 1146 "selector": { 1147 "matchLabels": { 1148 "app": "nginx" 1149 } 1150 }, 1151 "template": { 1152 "metadata": { 1153 "labels": { 1154 "app": "nginx" 1155 } 1156 }, 1157 "spec": { 1158 "containers": [{ 1159 "name": "nginx", 1160 "image": "nginx:latest" 1161 }] 1162 } 1163 } 1164 } 1165 }`) 1166 1167 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1168 AbsPath("/apis/apps/v1"). 1169 Namespace("default"). 1170 Resource("deployments"). 1171 Name("deployment"). 1172 Param("fieldManager", "apply_test"). 1173 Body(obj).Do(context.TODO()).Get() 1174 if err != nil { 1175 t.Fatalf("Failed to create object using Apply patch: %v", err) 1176 } 1177 1178 obj = []byte(`{ 1179 "apiVersion": "extensions/v1beta", 1180 "kind": "Deployment", 1181 "metadata": { 1182 "name": "deployment", 1183 "labels": {"app": "nginx"} 1184 }, 1185 "spec": { 1186 "replicas": 100, 1187 "selector": { 1188 "matchLabels": { 1189 "app": "nginx" 1190 } 1191 }, 1192 "template": { 1193 "metadata": { 1194 "labels": { 1195 "app": "nginx" 1196 } 1197 }, 1198 "spec": { 1199 "containers": [{ 1200 "name": "nginx", 1201 "image": "nginx:latest" 1202 }] 1203 } 1204 } 1205 } 1206 }`) 1207 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1208 AbsPath("/apis/apps/v1"). 1209 Namespace("default"). 1210 Resource("deployments"). 1211 Name("deployment"). 1212 Param("fieldManager", "apply_test"). 1213 Body([]byte(obj)).Do(context.TODO()).Get() 1214 if err == nil { 1215 t.Fatalf("Expecting to get version mismatch when applying object") 1216 } 1217 status, ok := err.(*apierrors.StatusError) 1218 if !ok { 1219 t.Fatalf("Expecting to get version mismatch as API error") 1220 } 1221 if status.Status().Code != http.StatusBadRequest { 1222 t.Fatalf("expected status code to be %d but was %d", http.StatusBadRequest, status.Status().Code) 1223 } 1224 } 1225 1226 // TestApplyConvertsManagedFieldsVersion checks that the apply 1227 // converts the API group-version in the field manager 1228 func TestApplyConvertsManagedFieldsVersion(t *testing.T) { 1229 client, closeFn := setup(t) 1230 defer closeFn() 1231 1232 obj := []byte(`{ 1233 "apiVersion": "apps/v1", 1234 "kind": "Deployment", 1235 "metadata": { 1236 "name": "deployment", 1237 "labels": {"app": "nginx"}, 1238 "managedFields": [ 1239 { 1240 "manager": "sidecar_controller", 1241 "operation": "Apply", 1242 "apiVersion": "extensions/v1beta1", 1243 "fieldsV1": { 1244 "f:metadata": { 1245 "f:labels": { 1246 "f:sidecar_version": {} 1247 } 1248 }, 1249 "f:spec": { 1250 "f:template": { 1251 "f: spec": { 1252 "f:containers": { 1253 "k:{\"name\":\"sidecar\"}": { 1254 ".": {}, 1255 "f:image": {} 1256 } 1257 } 1258 } 1259 } 1260 } 1261 } 1262 } 1263 ] 1264 }, 1265 "spec": { 1266 "selector": { 1267 "matchLabels": { 1268 "app": "nginx" 1269 } 1270 }, 1271 "template": { 1272 "metadata": { 1273 "labels": { 1274 "app": "nginx" 1275 } 1276 }, 1277 "spec": { 1278 "containers": [{ 1279 "name": "nginx", 1280 "image": "nginx:latest" 1281 }] 1282 } 1283 } 1284 } 1285 }`) 1286 1287 _, err := client.CoreV1().RESTClient().Post(). 1288 AbsPath("/apis/apps/v1"). 1289 Namespace("default"). 1290 Resource("deployments"). 1291 Body(obj).Do(context.TODO()).Get() 1292 if err != nil { 1293 t.Fatalf("Failed to create object: %v", err) 1294 } 1295 1296 obj = []byte(`{ 1297 "apiVersion": "apps/v1", 1298 "kind": "Deployment", 1299 "metadata": { 1300 "name": "deployment", 1301 "labels": {"sidecar_version": "release"} 1302 }, 1303 "spec": { 1304 "template": { 1305 "spec": { 1306 "containers": [{ 1307 "name": "sidecar", 1308 "image": "sidecar:latest" 1309 }] 1310 } 1311 } 1312 } 1313 }`) 1314 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1315 AbsPath("/apis/apps/v1"). 1316 Namespace("default"). 1317 Resource("deployments"). 1318 Name("deployment"). 1319 Param("fieldManager", "sidecar_controller"). 1320 Body([]byte(obj)).Do(context.TODO()).Get() 1321 if err != nil { 1322 t.Fatalf("Failed to apply object: %v", err) 1323 } 1324 1325 object, err := client.AppsV1().Deployments("default").Get(context.TODO(), "deployment", metav1.GetOptions{}) 1326 if err != nil { 1327 t.Fatalf("Failed to retrieve object: %v", err) 1328 } 1329 1330 accessor, err := meta.Accessor(object) 1331 if err != nil { 1332 t.Fatalf("Failed to get meta accessor: %v", err) 1333 } 1334 1335 managed := accessor.GetManagedFields() 1336 if len(managed) != 2 { 1337 t.Fatalf("Expected 2 field managers, but got managed fields: %v", managed) 1338 } 1339 1340 var actual *metav1.ManagedFieldsEntry 1341 for i := range managed { 1342 entry := &managed[i] 1343 if entry.Manager == "sidecar_controller" && entry.APIVersion == "apps/v1" { 1344 actual = entry 1345 } 1346 } 1347 1348 if actual == nil { 1349 t.Fatalf("Expected managed fields to contain entry with manager '%v' with converted api version '%v', but got managed fields:\n%v", "sidecar_controller", "apps/v1", managed) 1350 } 1351 1352 expected := &metav1.ManagedFieldsEntry{ 1353 Manager: "sidecar_controller", 1354 Operation: metav1.ManagedFieldsOperationApply, 1355 APIVersion: "apps/v1", 1356 Time: actual.Time, 1357 FieldsType: "FieldsV1", 1358 FieldsV1: &metav1.FieldsV1{ 1359 Raw: []byte(`{"f:metadata":{"f:labels":{"f:sidecar_version":{}}},"f:spec":{"f:template":{"f:spec":{"f:containers":{"k:{\"name\":\"sidecar\"}":{".":{},"f:image":{},"f:name":{}}}}}}}`), 1360 }, 1361 } 1362 1363 if !reflect.DeepEqual(actual, expected) { 1364 t.Fatalf("expected:\n%v\nbut got:\n%v", expected, actual) 1365 } 1366 } 1367 1368 // TestClearManagedFieldsWithMergePatch verifies it's possible to clear the managedFields 1369 func TestClearManagedFieldsWithMergePatch(t *testing.T) { 1370 client, closeFn := setup(t) 1371 defer closeFn() 1372 1373 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1374 Namespace("default"). 1375 Resource("configmaps"). 1376 Name("test-cm"). 1377 Param("fieldManager", "apply_test"). 1378 Body([]byte(`{ 1379 "apiVersion": "v1", 1380 "kind": "ConfigMap", 1381 "metadata": { 1382 "name": "test-cm", 1383 "namespace": "default", 1384 "labels": { 1385 "test-label": "test" 1386 } 1387 }, 1388 "data": { 1389 "key": "value" 1390 } 1391 }`)). 1392 Do(context.TODO()). 1393 Get() 1394 if err != nil { 1395 t.Fatalf("Failed to create object using Apply patch: %v", err) 1396 } 1397 1398 _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). 1399 Namespace("default"). 1400 Resource("configmaps"). 1401 Name("test-cm"). 1402 Body([]byte(`{"metadata":{"managedFields": [{}]}}`)).Do(context.TODO()).Get() 1403 if err != nil { 1404 t.Fatalf("Failed to patch object: %v", err) 1405 } 1406 1407 object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get() 1408 if err != nil { 1409 t.Fatalf("Failed to retrieve object: %v", err) 1410 } 1411 1412 accessor, err := meta.Accessor(object) 1413 if err != nil { 1414 t.Fatalf("Failed to get meta accessor: %v", err) 1415 } 1416 1417 if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 { 1418 t.Fatalf("Failed to clear managedFields, got: %v", managedFields) 1419 } 1420 } 1421 1422 // TestClearManagedFieldsWithStrategicMergePatch verifies it's possible to clear the managedFields 1423 func TestClearManagedFieldsWithStrategicMergePatch(t *testing.T) { 1424 client, closeFn := setup(t) 1425 defer closeFn() 1426 1427 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1428 Namespace("default"). 1429 Resource("configmaps"). 1430 Name("test-cm"). 1431 Param("fieldManager", "apply_test"). 1432 Body([]byte(`{ 1433 "apiVersion": "v1", 1434 "kind": "ConfigMap", 1435 "metadata": { 1436 "name": "test-cm", 1437 "namespace": "default", 1438 "labels": { 1439 "test-label": "test" 1440 } 1441 }, 1442 "data": { 1443 "key": "value" 1444 } 1445 }`)). 1446 Do(context.TODO()). 1447 Get() 1448 if err != nil { 1449 t.Fatalf("Failed to create object using Apply patch: %v", err) 1450 } 1451 1452 _, err = client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType). 1453 Namespace("default"). 1454 Resource("configmaps"). 1455 Name("test-cm"). 1456 Body([]byte(`{"metadata":{"managedFields": [{}]}}`)).Do(context.TODO()).Get() 1457 if err != nil { 1458 t.Fatalf("Failed to patch object: %v", err) 1459 } 1460 1461 object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get() 1462 if err != nil { 1463 t.Fatalf("Failed to retrieve object: %v", err) 1464 } 1465 1466 accessor, err := meta.Accessor(object) 1467 if err != nil { 1468 t.Fatalf("Failed to get meta accessor: %v", err) 1469 } 1470 1471 if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 { 1472 t.Fatalf("Failed to clear managedFields, got: %v", managedFields) 1473 } 1474 1475 if labels := accessor.GetLabels(); len(labels) < 1 { 1476 t.Fatalf("Expected other fields to stay untouched, got: %v", object) 1477 } 1478 } 1479 1480 // TestClearManagedFieldsWithJSONPatch verifies it's possible to clear the managedFields 1481 func TestClearManagedFieldsWithJSONPatch(t *testing.T) { 1482 client, closeFn := setup(t) 1483 defer closeFn() 1484 1485 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1486 Namespace("default"). 1487 Resource("configmaps"). 1488 Name("test-cm"). 1489 Param("fieldManager", "apply_test"). 1490 Body([]byte(`{ 1491 "apiVersion": "v1", 1492 "kind": "ConfigMap", 1493 "metadata": { 1494 "name": "test-cm", 1495 "namespace": "default", 1496 "labels": { 1497 "test-label": "test" 1498 } 1499 }, 1500 "data": { 1501 "key": "value" 1502 } 1503 }`)). 1504 Do(context.TODO()). 1505 Get() 1506 if err != nil { 1507 t.Fatalf("Failed to create object using Apply patch: %v", err) 1508 } 1509 1510 _, err = client.CoreV1().RESTClient().Patch(types.JSONPatchType). 1511 Namespace("default"). 1512 Resource("configmaps"). 1513 Name("test-cm"). 1514 Body([]byte(`[{"op": "replace", "path": "/metadata/managedFields", "value": [{}]}]`)).Do(context.TODO()).Get() 1515 if err != nil { 1516 t.Fatalf("Failed to patch object: %v", err) 1517 } 1518 1519 object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get() 1520 if err != nil { 1521 t.Fatalf("Failed to retrieve object: %v", err) 1522 } 1523 1524 accessor, err := meta.Accessor(object) 1525 if err != nil { 1526 t.Fatalf("Failed to get meta accessor: %v", err) 1527 } 1528 1529 if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 { 1530 t.Fatalf("Failed to clear managedFields, got: %v", managedFields) 1531 } 1532 } 1533 1534 // TestClearManagedFieldsWithUpdate verifies it's possible to clear the managedFields 1535 func TestClearManagedFieldsWithUpdate(t *testing.T) { 1536 client, closeFn := setup(t) 1537 defer closeFn() 1538 1539 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1540 Namespace("default"). 1541 Resource("configmaps"). 1542 Name("test-cm"). 1543 Param("fieldManager", "apply_test"). 1544 Body([]byte(`{ 1545 "apiVersion": "v1", 1546 "kind": "ConfigMap", 1547 "metadata": { 1548 "name": "test-cm", 1549 "namespace": "default", 1550 "labels": { 1551 "test-label": "test" 1552 } 1553 }, 1554 "data": { 1555 "key": "value" 1556 } 1557 }`)). 1558 Do(context.TODO()). 1559 Get() 1560 if err != nil { 1561 t.Fatalf("Failed to create object using Apply patch: %v", err) 1562 } 1563 1564 _, err = client.CoreV1().RESTClient().Put(). 1565 Namespace("default"). 1566 Resource("configmaps"). 1567 Name("test-cm"). 1568 Body([]byte(`{ 1569 "apiVersion": "v1", 1570 "kind": "ConfigMap", 1571 "metadata": { 1572 "name": "test-cm", 1573 "namespace": "default", 1574 "managedFields": [{}], 1575 "labels": { 1576 "test-label": "test" 1577 } 1578 }, 1579 "data": { 1580 "key": "value" 1581 } 1582 }`)).Do(context.TODO()).Get() 1583 if err != nil { 1584 t.Fatalf("Failed to patch object: %v", err) 1585 } 1586 1587 object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get() 1588 if err != nil { 1589 t.Fatalf("Failed to retrieve object: %v", err) 1590 } 1591 1592 accessor, err := meta.Accessor(object) 1593 if err != nil { 1594 t.Fatalf("Failed to get meta accessor: %v", err) 1595 } 1596 1597 if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 { 1598 t.Fatalf("Failed to clear managedFields, got: %v", managedFields) 1599 } 1600 1601 if labels := accessor.GetLabels(); len(labels) < 1 { 1602 t.Fatalf("Expected other fields to stay untouched, got: %v", object) 1603 } 1604 } 1605 1606 // TestErrorsDontFail 1607 func TestErrorsDontFail(t *testing.T) { 1608 client, closeFn := setup(t) 1609 defer closeFn() 1610 1611 // Tries to create with a managed fields that has an empty `fieldsType`. 1612 _, err := client.CoreV1().RESTClient().Post(). 1613 Namespace("default"). 1614 Resource("configmaps"). 1615 Param("fieldManager", "apply_test"). 1616 Body([]byte(`{ 1617 "apiVersion": "v1", 1618 "kind": "ConfigMap", 1619 "metadata": { 1620 "name": "test-cm", 1621 "namespace": "default", 1622 "managedFields": [{ 1623 "manager": "apply_test", 1624 "operation": "Apply", 1625 "apiVersion": "v1", 1626 "time": "2019-07-08T09:31:18Z", 1627 "fieldsType": "", 1628 "fieldsV1": {} 1629 }], 1630 "labels": { 1631 "test-label": "test" 1632 } 1633 }, 1634 "data": { 1635 "key": "value" 1636 } 1637 }`)). 1638 Do(context.TODO()). 1639 Get() 1640 if err != nil { 1641 t.Fatalf("Failed to create object with empty fieldsType: %v", err) 1642 } 1643 } 1644 1645 func TestErrorsDontFailUpdate(t *testing.T) { 1646 client, closeFn := setup(t) 1647 defer closeFn() 1648 1649 _, err := client.CoreV1().RESTClient().Post(). 1650 Namespace("default"). 1651 Resource("configmaps"). 1652 Param("fieldManager", "apply_test"). 1653 Body([]byte(`{ 1654 "apiVersion": "v1", 1655 "kind": "ConfigMap", 1656 "metadata": { 1657 "name": "test-cm", 1658 "namespace": "default", 1659 "labels": { 1660 "test-label": "test" 1661 } 1662 }, 1663 "data": { 1664 "key": "value" 1665 } 1666 }`)). 1667 Do(context.TODO()). 1668 Get() 1669 if err != nil { 1670 t.Fatalf("Failed to create object: %v", err) 1671 } 1672 1673 _, err = client.CoreV1().RESTClient().Put(). 1674 Namespace("default"). 1675 Resource("configmaps"). 1676 Name("test-cm"). 1677 Param("fieldManager", "apply_test"). 1678 Body([]byte(`{ 1679 "apiVersion": "v1", 1680 "kind": "ConfigMap", 1681 "metadata": { 1682 "name": "test-cm", 1683 "namespace": "default", 1684 "managedFields": [{ 1685 "manager": "apply_test", 1686 "operation": "Apply", 1687 "apiVersion": "v1", 1688 "time": "2019-07-08T09:31:18Z", 1689 "fieldsType": "", 1690 "fieldsV1": {} 1691 }], 1692 "labels": { 1693 "test-label": "test" 1694 } 1695 }, 1696 "data": { 1697 "key": "value" 1698 } 1699 }`)). 1700 Do(context.TODO()). 1701 Get() 1702 if err != nil { 1703 t.Fatalf("Failed to update object with empty fieldsType: %v", err) 1704 } 1705 } 1706 1707 func TestErrorsDontFailPatch(t *testing.T) { 1708 client, closeFn := setup(t) 1709 defer closeFn() 1710 1711 _, err := client.CoreV1().RESTClient().Post(). 1712 Namespace("default"). 1713 Resource("configmaps"). 1714 Param("fieldManager", "apply_test"). 1715 Body([]byte(`{ 1716 "apiVersion": "v1", 1717 "kind": "ConfigMap", 1718 "metadata": { 1719 "name": "test-cm", 1720 "namespace": "default", 1721 "labels": { 1722 "test-label": "test" 1723 } 1724 }, 1725 "data": { 1726 "key": "value" 1727 } 1728 }`)). 1729 Do(context.TODO()). 1730 Get() 1731 if err != nil { 1732 t.Fatalf("Failed to create object: %v", err) 1733 } 1734 1735 _, err = client.CoreV1().RESTClient().Patch(types.JSONPatchType). 1736 Namespace("default"). 1737 Resource("configmaps"). 1738 Name("test-cm"). 1739 Param("fieldManager", "apply_test"). 1740 Body([]byte(`[{"op": "replace", "path": "/metadata/managedFields", "value": [{ 1741 "manager": "apply_test", 1742 "operation": "Apply", 1743 "apiVersion": "v1", 1744 "time": "2019-07-08T09:31:18Z", 1745 "fieldsType": "", 1746 "fieldsV1": {} 1747 }]}]`)). 1748 Do(context.TODO()). 1749 Get() 1750 if err != nil { 1751 t.Fatalf("Failed to patch object with empty FieldsType: %v", err) 1752 } 1753 } 1754 1755 func TestApplyDoesNotChangeManagedFieldsViaSubresources(t *testing.T) { 1756 client, closeFn := setup(t) 1757 defer closeFn() 1758 1759 podBytes := []byte(`{ 1760 "apiVersion": "v1", 1761 "kind": "Pod", 1762 "metadata": { 1763 "name": "just-a-pod" 1764 }, 1765 "spec": { 1766 "containers": [{ 1767 "name": "test-container-a", 1768 "image": "test-image-one" 1769 }] 1770 } 1771 }`) 1772 1773 liveObj, err := client.CoreV1().RESTClient(). 1774 Patch(types.ApplyPatchType). 1775 Namespace("default"). 1776 Param("fieldManager", "apply_test"). 1777 Resource("pods"). 1778 Name("just-a-pod"). 1779 Body(podBytes). 1780 Do(context.TODO()). 1781 Get() 1782 if err != nil { 1783 t.Fatalf("Failed to create object: %v", err) 1784 } 1785 1786 updateBytes := []byte(`{ 1787 "metadata": { 1788 "managedFields": [{ 1789 "manager":"testing", 1790 "operation":"Update", 1791 "apiVersion":"v1", 1792 "fieldsType":"FieldsV1", 1793 "fieldsV1":{ 1794 "f:spec":{ 1795 "f:containers":{ 1796 "k:{\"name\":\"testing\"}":{ 1797 ".":{}, 1798 "f:image":{}, 1799 "f:name":{} 1800 } 1801 } 1802 } 1803 } 1804 }] 1805 }, 1806 "status": { 1807 "conditions": [{"type": "MyStatus", "status":"true"}] 1808 } 1809 }`) 1810 1811 updateActor := "update_managedfields_test" 1812 newObj, err := client.CoreV1().RESTClient(). 1813 Patch(types.MergePatchType). 1814 Namespace("default"). 1815 Param("fieldManager", updateActor). 1816 Name("just-a-pod"). 1817 Resource("pods"). 1818 SubResource("status"). 1819 Body(updateBytes). 1820 Do(context.TODO()). 1821 Get() 1822 1823 if err != nil { 1824 t.Fatalf("Error updating subresource: %v ", err) 1825 } 1826 1827 liveAccessor, err := meta.Accessor(liveObj) 1828 if err != nil { 1829 t.Fatalf("Failed to get meta accessor for live object: %v", err) 1830 } 1831 newAccessor, err := meta.Accessor(newObj) 1832 if err != nil { 1833 t.Fatalf("Failed to get meta accessor for new object: %v", err) 1834 } 1835 1836 liveManagedFields := liveAccessor.GetManagedFields() 1837 if len(liveManagedFields) != 1 { 1838 t.Fatalf("Expected managedFields in the live object to have exactly one entry, got %d: %v", len(liveManagedFields), liveManagedFields) 1839 } 1840 1841 newManagedFields := newAccessor.GetManagedFields() 1842 if len(newManagedFields) != 2 { 1843 t.Fatalf("Expected managedFields in the new object to have exactly two entries, got %d: %v", len(newManagedFields), newManagedFields) 1844 } 1845 1846 if !reflect.DeepEqual(liveManagedFields[0], newManagedFields[0]) { 1847 t.Fatalf("managedFields updated via subresource:\n\nlive managedFields: %v\nnew managedFields: %v\n\n", liveManagedFields, newManagedFields) 1848 } 1849 1850 if newManagedFields[1].Manager != updateActor { 1851 t.Fatalf(`Expected managerFields to have an entry with manager set to %q`, updateActor) 1852 } 1853 } 1854 1855 // TestClearManagedFieldsWithUpdateEmptyList verifies it's possible to clear the managedFields by sending an empty list. 1856 func TestClearManagedFieldsWithUpdateEmptyList(t *testing.T) { 1857 client, closeFn := setup(t) 1858 defer closeFn() 1859 1860 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1861 Namespace("default"). 1862 Resource("configmaps"). 1863 Name("test-cm"). 1864 Param("fieldManager", "apply_test"). 1865 Body([]byte(`{ 1866 "apiVersion": "v1", 1867 "kind": "ConfigMap", 1868 "metadata": { 1869 "name": "test-cm", 1870 "namespace": "default", 1871 "labels": { 1872 "test-label": "test" 1873 } 1874 }, 1875 "data": { 1876 "key": "value" 1877 } 1878 }`)). 1879 Do(context.TODO()). 1880 Get() 1881 if err != nil { 1882 t.Fatalf("Failed to create object using Apply patch: %v", err) 1883 } 1884 1885 _, err = client.CoreV1().RESTClient().Put(). 1886 Namespace("default"). 1887 Resource("configmaps"). 1888 Name("test-cm"). 1889 Body([]byte(`{ 1890 "apiVersion": "v1", 1891 "kind": "ConfigMap", 1892 "metadata": { 1893 "name": "test-cm", 1894 "namespace": "default", 1895 "managedFields": [], 1896 "labels": { 1897 "test-label": "test" 1898 } 1899 }, 1900 "data": { 1901 "key": "value" 1902 } 1903 }`)).Do(context.TODO()).Get() 1904 if err != nil { 1905 t.Fatalf("Failed to patch object: %v", err) 1906 } 1907 1908 _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType). 1909 Namespace("default"). 1910 Resource("configmaps"). 1911 Name("test-cm"). 1912 Body([]byte(`{"metadata":{"labels": { "test-label": "v1" }}}`)).Do(context.TODO()).Get() 1913 1914 if err != nil { 1915 t.Fatalf("Failed to patch object: %v", err) 1916 } 1917 1918 object, err := client.CoreV1().RESTClient().Get().Namespace("default").Resource("configmaps").Name("test-cm").Do(context.TODO()).Get() 1919 if err != nil { 1920 t.Fatalf("Failed to retrieve object: %v", err) 1921 } 1922 1923 accessor, err := meta.Accessor(object) 1924 if err != nil { 1925 t.Fatalf("Failed to get meta accessor: %v", err) 1926 } 1927 1928 if managedFields := accessor.GetManagedFields(); len(managedFields) != 0 { 1929 t.Fatalf("Failed to stop tracking managedFields, got: %v", managedFields) 1930 } 1931 1932 if labels := accessor.GetLabels(); len(labels) < 1 { 1933 t.Fatalf("Expected other fields to stay untouched, got: %v", object) 1934 } 1935 } 1936 1937 // TestApplyUnsetExclusivelyOwnedFields verifies that when owned fields are omitted from an applied 1938 // configuration, and no other managers own the field, it is removed. 1939 func TestApplyUnsetExclusivelyOwnedFields(t *testing.T) { 1940 client, closeFn := setup(t) 1941 defer closeFn() 1942 1943 // spec.replicas is a optional, defaulted field 1944 // spec.template.spec.hostname is an optional, non-defaulted field 1945 apply := []byte(`{ 1946 "apiVersion": "apps/v1", 1947 "kind": "Deployment", 1948 "metadata": { 1949 "name": "deployment-exclusive-unset", 1950 "labels": {"app": "nginx"} 1951 }, 1952 "spec": { 1953 "replicas": 3, 1954 "selector": { 1955 "matchLabels": { 1956 "app": "nginx" 1957 } 1958 }, 1959 "template": { 1960 "metadata": { 1961 "labels": { 1962 "app": "nginx" 1963 } 1964 }, 1965 "spec": { 1966 "hostname": "test-hostname", 1967 "containers": [{ 1968 "name": "nginx", 1969 "image": "nginx:latest" 1970 }] 1971 } 1972 } 1973 } 1974 }`) 1975 1976 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 1977 AbsPath("/apis/apps/v1"). 1978 Namespace("default"). 1979 Resource("deployments"). 1980 Name("deployment-exclusive-unset"). 1981 Param("fieldManager", "apply_test"). 1982 Body(apply). 1983 Do(context.TODO()). 1984 Get() 1985 if err != nil { 1986 t.Fatalf("Failed to create object using Apply patch: %v", err) 1987 } 1988 1989 // unset spec.replicas and spec.template.spec.hostname 1990 apply = []byte(`{ 1991 "apiVersion": "apps/v1", 1992 "kind": "Deployment", 1993 "metadata": { 1994 "name": "deployment-exclusive-unset", 1995 "labels": {"app": "nginx"} 1996 }, 1997 "spec": { 1998 "selector": { 1999 "matchLabels": { 2000 "app": "nginx" 2001 } 2002 }, 2003 "template": { 2004 "metadata": { 2005 "labels": { 2006 "app": "nginx" 2007 } 2008 }, 2009 "spec": { 2010 "containers": [{ 2011 "name": "nginx", 2012 "image": "nginx:latest" 2013 }] 2014 } 2015 } 2016 } 2017 }`) 2018 2019 patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2020 AbsPath("/apis/apps/v1"). 2021 Namespace("default"). 2022 Resource("deployments"). 2023 Name("deployment-exclusive-unset"). 2024 Param("fieldManager", "apply_test"). 2025 Body(apply). 2026 Do(context.TODO()). 2027 Get() 2028 if err != nil { 2029 t.Fatalf("Failed to create object using Apply patch: %v", err) 2030 } 2031 2032 deployment, ok := patched.(*appsv1.Deployment) 2033 if !ok { 2034 t.Fatalf("Failed to convert response object to Deployment") 2035 } 2036 if *deployment.Spec.Replicas != 1 { 2037 t.Errorf("Expected deployment.spec.replicas to be 1 (default value), but got %d", deployment.Spec.Replicas) 2038 } 2039 if len(deployment.Spec.Template.Spec.Hostname) != 0 { 2040 t.Errorf("Expected deployment.spec.template.spec.hostname to be unset, but got %s", deployment.Spec.Template.Spec.Hostname) 2041 } 2042 } 2043 2044 // TestApplyUnsetSharedFields verifies that when owned fields are omitted from an applied 2045 // configuration, but other managers also own the field, is it not removed. 2046 func TestApplyUnsetSharedFields(t *testing.T) { 2047 client, closeFn := setup(t) 2048 defer closeFn() 2049 2050 // spec.replicas is a optional, defaulted field 2051 // spec.template.spec.hostname is an optional, non-defaulted field 2052 apply := []byte(`{ 2053 "apiVersion": "apps/v1", 2054 "kind": "Deployment", 2055 "metadata": { 2056 "name": "deployment-shared-unset", 2057 "labels": {"app": "nginx"} 2058 }, 2059 "spec": { 2060 "replicas": 3, 2061 "selector": { 2062 "matchLabels": { 2063 "app": "nginx" 2064 } 2065 }, 2066 "template": { 2067 "metadata": { 2068 "labels": { 2069 "app": "nginx" 2070 } 2071 }, 2072 "spec": { 2073 "hostname": "test-hostname", 2074 "containers": [{ 2075 "name": "nginx", 2076 "image": "nginx:latest" 2077 }] 2078 } 2079 } 2080 } 2081 }`) 2082 2083 for _, fieldManager := range []string{"shared_owner_1", "shared_owner_2"} { 2084 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2085 AbsPath("/apis/apps/v1"). 2086 Namespace("default"). 2087 Resource("deployments"). 2088 Name("deployment-shared-unset"). 2089 Param("fieldManager", fieldManager). 2090 Body(apply). 2091 Do(context.TODO()). 2092 Get() 2093 if err != nil { 2094 t.Fatalf("Failed to create object using Apply patch: %v", err) 2095 } 2096 } 2097 2098 // unset spec.replicas and spec.template.spec.hostname 2099 apply = []byte(`{ 2100 "apiVersion": "apps/v1", 2101 "kind": "Deployment", 2102 "metadata": { 2103 "name": "deployment-shared-unset", 2104 "labels": {"app": "nginx"} 2105 }, 2106 "spec": { 2107 "selector": { 2108 "matchLabels": { 2109 "app": "nginx" 2110 } 2111 }, 2112 "template": { 2113 "metadata": { 2114 "labels": { 2115 "app": "nginx" 2116 } 2117 }, 2118 "spec": { 2119 "containers": [{ 2120 "name": "nginx", 2121 "image": "nginx:latest" 2122 }] 2123 } 2124 } 2125 } 2126 }`) 2127 2128 patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2129 AbsPath("/apis/apps/v1"). 2130 Namespace("default"). 2131 Resource("deployments"). 2132 Name("deployment-shared-unset"). 2133 Param("fieldManager", "shared_owner_1"). 2134 Body(apply). 2135 Do(context.TODO()). 2136 Get() 2137 if err != nil { 2138 t.Fatalf("Failed to create object using Apply patch: %v", err) 2139 } 2140 2141 deployment, ok := patched.(*appsv1.Deployment) 2142 if !ok { 2143 t.Fatalf("Failed to convert response object to Deployment") 2144 } 2145 if *deployment.Spec.Replicas != 3 { 2146 t.Errorf("Expected deployment.spec.replicas to be 3, but got %d", deployment.Spec.Replicas) 2147 } 2148 if deployment.Spec.Template.Spec.Hostname != "test-hostname" { 2149 t.Errorf("Expected deployment.spec.template.spec.hostname to be \"test-hostname\", but got %s", deployment.Spec.Template.Spec.Hostname) 2150 } 2151 } 2152 2153 // TestApplyCanTransferFieldOwnershipToController verifies that when an applier creates an 2154 // object, a controller takes ownership of a field, and the applier 2155 // then omits the field from its applied configuration, that the field value persists. 2156 func TestApplyCanTransferFieldOwnershipToController(t *testing.T) { 2157 client, closeFn := setup(t) 2158 defer closeFn() 2159 2160 // Applier creates a deployment with replicas set to 3 2161 apply := []byte(`{ 2162 "apiVersion": "apps/v1", 2163 "kind": "Deployment", 2164 "metadata": { 2165 "name": "deployment-shared-map-item-removal", 2166 "labels": {"app": "nginx"} 2167 }, 2168 "spec": { 2169 "replicas": 3, 2170 "selector": { 2171 "matchLabels": { 2172 "app": "nginx" 2173 } 2174 }, 2175 "template": { 2176 "metadata": { 2177 "labels": { 2178 "app": "nginx" 2179 } 2180 }, 2181 "spec": { 2182 "containers": [{ 2183 "name": "nginx", 2184 "image": "nginx:latest", 2185 }] 2186 } 2187 } 2188 } 2189 }`) 2190 2191 appliedObj, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2192 AbsPath("/apis/apps/v1"). 2193 Namespace("default"). 2194 Resource("deployments"). 2195 Name("deployment-shared-map-item-removal"). 2196 Param("fieldManager", "test_applier"). 2197 Body(apply). 2198 Do(context.TODO()). 2199 Get() 2200 if err != nil { 2201 t.Fatalf("Failed to create object using Apply patch: %v", err) 2202 } 2203 2204 // a controller takes over the replicas field 2205 applied, ok := appliedObj.(*appsv1.Deployment) 2206 if !ok { 2207 t.Fatalf("Failed to convert response object to Deployment") 2208 } 2209 replicas := int32(4) 2210 applied.Spec.Replicas = &replicas 2211 _, err = client.AppsV1().Deployments("default"). 2212 Update(context.TODO(), applied, metav1.UpdateOptions{FieldManager: "test_updater"}) 2213 if err != nil { 2214 t.Fatalf("Failed to create object using Apply patch: %v", err) 2215 } 2216 2217 // applier omits replicas 2218 apply = []byte(`{ 2219 "apiVersion": "apps/v1", 2220 "kind": "Deployment", 2221 "metadata": { 2222 "name": "deployment-shared-map-item-removal", 2223 "labels": {"app": "nginx"} 2224 }, 2225 "spec": { 2226 "selector": { 2227 "matchLabels": { 2228 "app": "nginx" 2229 } 2230 }, 2231 "template": { 2232 "metadata": { 2233 "labels": { 2234 "app": "nginx" 2235 } 2236 }, 2237 "spec": { 2238 "containers": [{ 2239 "name": "nginx", 2240 "image": "nginx:latest", 2241 }] 2242 } 2243 } 2244 } 2245 }`) 2246 2247 patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2248 AbsPath("/apis/apps/v1"). 2249 Namespace("default"). 2250 Resource("deployments"). 2251 Name("deployment-shared-map-item-removal"). 2252 Param("fieldManager", "test_applier"). 2253 Body(apply). 2254 Do(context.TODO()). 2255 Get() 2256 if err != nil { 2257 t.Fatalf("Failed to create object using Apply patch: %v", err) 2258 } 2259 2260 // ensure the container is deleted even though a controller updated a field of the container 2261 deployment, ok := patched.(*appsv1.Deployment) 2262 if !ok { 2263 t.Fatalf("Failed to convert response object to Deployment") 2264 } 2265 if *deployment.Spec.Replicas != 4 { 2266 t.Errorf("Expected deployment.spec.replicas to be 4, but got %d", deployment.Spec.Replicas) 2267 } 2268 } 2269 2270 // TestApplyCanRemoveMapItemsContributedToByControllers verifies that when an applier creates an 2271 // object, a controller modifies the contents of the map item via update, and the applier 2272 // then omits the item from its applied configuration, that the item is removed. 2273 func TestApplyCanRemoveMapItemsContributedToByControllers(t *testing.T) { 2274 client, closeFn := setup(t) 2275 defer closeFn() 2276 2277 // Applier creates a deployment with a name=nginx container 2278 apply := []byte(`{ 2279 "apiVersion": "apps/v1", 2280 "kind": "Deployment", 2281 "metadata": { 2282 "name": "deployment-shared-map-item-removal", 2283 "labels": {"app": "nginx"} 2284 }, 2285 "spec": { 2286 "selector": { 2287 "matchLabels": { 2288 "app": "nginx" 2289 } 2290 }, 2291 "template": { 2292 "metadata": { 2293 "labels": { 2294 "app": "nginx" 2295 } 2296 }, 2297 "spec": { 2298 "containers": [{ 2299 "name": "nginx", 2300 "image": "nginx:latest", 2301 }] 2302 } 2303 } 2304 } 2305 }`) 2306 2307 appliedObj, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2308 AbsPath("/apis/apps/v1"). 2309 Namespace("default"). 2310 Resource("deployments"). 2311 Name("deployment-shared-map-item-removal"). 2312 Param("fieldManager", "test_applier"). 2313 Body(apply). 2314 Do(context.TODO()). 2315 Get() 2316 if err != nil { 2317 t.Fatalf("Failed to create object using Apply patch: %v", err) 2318 } 2319 2320 // a controller sets container.workingDir of the name=nginx container via an update 2321 applied, ok := appliedObj.(*appsv1.Deployment) 2322 if !ok { 2323 t.Fatalf("Failed to convert response object to Deployment") 2324 } 2325 applied.Spec.Template.Spec.Containers[0].WorkingDir = "/home/replacement" 2326 _, err = client.AppsV1().Deployments("default"). 2327 Update(context.TODO(), applied, metav1.UpdateOptions{FieldManager: "test_updater"}) 2328 if err != nil { 2329 t.Fatalf("Failed to create object using Apply patch: %v", err) 2330 } 2331 2332 // applier removes name=nginx the container 2333 apply = []byte(`{ 2334 "apiVersion": "apps/v1", 2335 "kind": "Deployment", 2336 "metadata": { 2337 "name": "deployment-shared-map-item-removal", 2338 "labels": {"app": "nginx"} 2339 }, 2340 "spec": { 2341 "replicas": 3, 2342 "selector": { 2343 "matchLabels": { 2344 "app": "nginx" 2345 } 2346 }, 2347 "template": { 2348 "metadata": { 2349 "labels": { 2350 "app": "nginx" 2351 } 2352 }, 2353 "spec": { 2354 "hostname": "test-hostname", 2355 "containers": [{ 2356 "name": "other-container", 2357 "image": "nginx:latest", 2358 }] 2359 } 2360 } 2361 } 2362 }`) 2363 2364 patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2365 AbsPath("/apis/apps/v1"). 2366 Namespace("default"). 2367 Resource("deployments"). 2368 Name("deployment-shared-map-item-removal"). 2369 Param("fieldManager", "test_applier"). 2370 Body(apply). 2371 Do(context.TODO()). 2372 Get() 2373 if err != nil { 2374 t.Fatalf("Failed to create object using Apply patch: %v", err) 2375 } 2376 2377 // ensure the container is deleted even though a controller updated a field of the container 2378 deployment, ok := patched.(*appsv1.Deployment) 2379 if !ok { 2380 t.Fatalf("Failed to convert response object to Deployment") 2381 } 2382 if len(deployment.Spec.Template.Spec.Containers) != 1 { 2383 t.Fatalf("Expected 1 container after apply, got %d", len(deployment.Spec.Template.Spec.Containers)) 2384 } 2385 if deployment.Spec.Template.Spec.Containers[0].Name != "other-container" { 2386 t.Fatalf("Expected container to be named \"other-container\" but got %s", deployment.Spec.Template.Spec.Containers[0].Name) 2387 } 2388 } 2389 2390 // TestDefaultMissingKeys makes sure that the missing keys default is used when merging. 2391 func TestDefaultMissingKeys(t *testing.T) { 2392 client, closeFn := setup(t) 2393 defer closeFn() 2394 2395 // Applier creates a deployment with containerPort but no protocol 2396 apply := []byte(`{ 2397 "apiVersion": "apps/v1", 2398 "kind": "Deployment", 2399 "metadata": { 2400 "name": "deployment-shared-map-item-removal", 2401 "labels": {"app": "nginx"} 2402 }, 2403 "spec": { 2404 "selector": { 2405 "matchLabels": { 2406 "app": "nginx" 2407 } 2408 }, 2409 "template": { 2410 "metadata": { 2411 "labels": { 2412 "app": "nginx" 2413 } 2414 }, 2415 "spec": { 2416 "containers": [{ 2417 "name": "nginx", 2418 "image": "nginx:latest", 2419 "ports": [{ 2420 "name": "foo", 2421 "containerPort": 80 2422 }] 2423 }] 2424 } 2425 } 2426 } 2427 }`) 2428 2429 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2430 AbsPath("/apis/apps/v1"). 2431 Namespace("default"). 2432 Resource("deployments"). 2433 Name("deployment-shared-map-item-removal"). 2434 Param("fieldManager", "test_applier"). 2435 Body(apply). 2436 Do(context.TODO()). 2437 Get() 2438 if err != nil { 2439 t.Fatalf("Failed to create object using Apply patch: %v", err) 2440 } 2441 2442 // Applier updates the name, and uses the protocol, we should get a conflict. 2443 apply = []byte(`{ 2444 "apiVersion": "apps/v1", 2445 "kind": "Deployment", 2446 "metadata": { 2447 "name": "deployment-shared-map-item-removal", 2448 "labels": {"app": "nginx"} 2449 }, 2450 "spec": { 2451 "selector": { 2452 "matchLabels": { 2453 "app": "nginx" 2454 } 2455 }, 2456 "template": { 2457 "metadata": { 2458 "labels": { 2459 "app": "nginx" 2460 } 2461 }, 2462 "spec": { 2463 "containers": [{ 2464 "name": "nginx", 2465 "image": "nginx:latest", 2466 "ports": [{ 2467 "name": "bar", 2468 "containerPort": 80, 2469 "protocol": "TCP" 2470 }] 2471 }] 2472 } 2473 } 2474 } 2475 }`) 2476 patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2477 AbsPath("/apis/apps/v1"). 2478 Namespace("default"). 2479 Resource("deployments"). 2480 Name("deployment-shared-map-item-removal"). 2481 Param("fieldManager", "test_applier_conflict"). 2482 Body(apply). 2483 Do(context.TODO()). 2484 Get() 2485 if err == nil { 2486 t.Fatalf("Expecting to get conflicts when a different applier updates existing list item, got no error: %s", patched) 2487 } 2488 status, ok := err.(*apierrors.StatusError) 2489 if !ok { 2490 t.Fatalf("Expecting to get conflicts as API error") 2491 } 2492 if len(status.Status().Details.Causes) != 1 { 2493 t.Fatalf("Expecting to get one conflict when a different applier updates existing list item, got: %v", status.Status().Details.Causes) 2494 } 2495 } 2496 2497 var podBytes = []byte(` 2498 apiVersion: v1 2499 kind: Pod 2500 metadata: 2501 labels: 2502 app: some-app 2503 plugin1: some-value 2504 plugin2: some-value 2505 plugin3: some-value 2506 plugin4: some-value 2507 name: some-name 2508 namespace: default 2509 ownerReferences: 2510 - apiVersion: apps/v1 2511 blockOwnerDeletion: true 2512 controller: true 2513 kind: ReplicaSet 2514 name: some-name 2515 uid: 0a9d2b9e-779e-11e7-b422-42010a8001be 2516 spec: 2517 containers: 2518 - args: 2519 - one 2520 - two 2521 - three 2522 - four 2523 - five 2524 - six 2525 - seven 2526 - eight 2527 - nine 2528 env: 2529 - name: VAR_3 2530 valueFrom: 2531 secretKeyRef: 2532 key: some-other-key 2533 name: some-oher-name 2534 - name: VAR_2 2535 valueFrom: 2536 secretKeyRef: 2537 key: other-key 2538 name: other-name 2539 - name: VAR_1 2540 valueFrom: 2541 secretKeyRef: 2542 key: some-key 2543 name: some-name 2544 image: some-image-name 2545 imagePullPolicy: IfNotPresent 2546 name: some-name 2547 resources: 2548 requests: 2549 cpu: "0" 2550 terminationMessagePath: /dev/termination-log 2551 terminationMessagePolicy: File 2552 volumeMounts: 2553 - mountPath: /var/run/secrets/kubernetes.io/serviceaccount 2554 name: default-token-hu5jz 2555 readOnly: true 2556 dnsPolicy: ClusterFirst 2557 nodeName: node-name 2558 priority: 0 2559 restartPolicy: Always 2560 schedulerName: default-scheduler 2561 securityContext: {} 2562 serviceAccount: default 2563 serviceAccountName: default 2564 terminationGracePeriodSeconds: 30 2565 tolerations: 2566 - effect: NoExecute 2567 key: node.kubernetes.io/not-ready 2568 operator: Exists 2569 tolerationSeconds: 300 2570 - effect: NoExecute 2571 key: node.kubernetes.io/unreachable 2572 operator: Exists 2573 tolerationSeconds: 300 2574 volumes: 2575 - name: default-token-hu5jz 2576 secret: 2577 defaultMode: 420 2578 secretName: default-token-hu5jz 2579 status: 2580 conditions: 2581 - lastProbeTime: null 2582 lastTransitionTime: "2019-07-08T09:31:18Z" 2583 status: "True" 2584 type: Initialized 2585 - lastProbeTime: null 2586 lastTransitionTime: "2019-07-08T09:41:59Z" 2587 status: "True" 2588 type: Ready 2589 - lastProbeTime: null 2590 lastTransitionTime: null 2591 status: "True" 2592 type: ContainersReady 2593 - lastProbeTime: null 2594 lastTransitionTime: "2019-07-08T09:31:18Z" 2595 status: "True" 2596 type: PodScheduled 2597 containerStatuses: 2598 - containerID: docker://885e82a1ed0b7356541bb410a0126921ac42439607c09875cd8097dd5d7b5376 2599 image: some-image-name 2600 imageID: docker-pullable://some-image-id 2601 lastState: 2602 terminated: 2603 containerID: docker://d57290f9e00fad626b20d2dd87a3cf69bbc22edae07985374f86a8b2b4e39565 2604 exitCode: 255 2605 finishedAt: "2019-07-08T09:39:09Z" 2606 reason: Error 2607 startedAt: "2019-07-08T09:38:54Z" 2608 name: name 2609 ready: true 2610 restartCount: 6 2611 state: 2612 running: 2613 startedAt: "2019-07-08T09:41:59Z" 2614 hostIP: 10.0.0.1 2615 phase: Running 2616 podIP: 10.0.0.1 2617 qosClass: BestEffort 2618 startTime: "2019-07-08T09:31:18Z" 2619 `) 2620 2621 func decodePod(podBytes []byte) v1.Pod { 2622 pod := v1.Pod{} 2623 err := yaml.Unmarshal(podBytes, &pod) 2624 if err != nil { 2625 panic(err) 2626 } 2627 return pod 2628 } 2629 2630 func encodePod(pod v1.Pod) []byte { 2631 podBytes, err := yaml.Marshal(pod) 2632 if err != nil { 2633 panic(err) 2634 } 2635 return podBytes 2636 } 2637 2638 func getPodBytesWhenEnabled(b *testing.B, pod v1.Pod, format string) []byte { 2639 client, closeFn := setup(b) 2640 defer closeFn() 2641 flag.Lookup("v").Value.Set("0") 2642 2643 pod.Name = "size-pod" 2644 podB, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2645 Name(pod.Name). 2646 Namespace("default"). 2647 Param("fieldManager", "apply_test"). 2648 Resource("pods"). 2649 SetHeader("Accept", format). 2650 Body(encodePod(pod)).DoRaw(context.TODO()) 2651 if err != nil { 2652 b.Fatalf("Failed to create object: %#v", err) 2653 } 2654 return podB 2655 } 2656 2657 func BenchmarkServerSideApply(b *testing.B) { 2658 podBytesWhenEnabled := getPodBytesWhenEnabled(b, decodePod(podBytes), "application/yaml") 2659 2660 client, closeFn := setup(b) 2661 defer closeFn() 2662 flag.Lookup("v").Value.Set("0") 2663 2664 benchAll(b, client, decodePod(podBytesWhenEnabled)) 2665 } 2666 2667 func benchAll(b *testing.B, client clientset.Interface, pod v1.Pod) { 2668 // Make sure pod is ready to post 2669 pod.ObjectMeta.CreationTimestamp = metav1.Time{} 2670 pod.ObjectMeta.ResourceVersion = "" 2671 pod.ObjectMeta.UID = "" 2672 2673 // Create pod for repeated-updates 2674 pod.Name = "repeated-pod" 2675 _, err := client.CoreV1().RESTClient().Post(). 2676 Namespace("default"). 2677 Resource("pods"). 2678 SetHeader("Content-Type", "application/yaml"). 2679 Body(encodePod(pod)).Do(context.TODO()).Get() 2680 if err != nil { 2681 b.Fatalf("Failed to create object: %v", err) 2682 } 2683 2684 b.Run("List1", benchListPod(client, pod, 1)) 2685 b.Run("List20", benchListPod(client, pod, 20)) 2686 b.Run("List200", benchListPod(client, pod, 200)) 2687 b.Run("List2000", benchListPod(client, pod, 2000)) 2688 2689 b.Run("RepeatedUpdates", benchRepeatedUpdate(client, "repeated-pod")) 2690 b.Run("Post1", benchPostPod(client, pod, 1)) 2691 b.Run("Post10", benchPostPod(client, pod, 10)) 2692 b.Run("Post50", benchPostPod(client, pod, 50)) 2693 } 2694 2695 func benchPostPod(client clientset.Interface, pod v1.Pod, parallel int) func(*testing.B) { 2696 return func(b *testing.B) { 2697 b.ResetTimer() 2698 b.ReportAllocs() 2699 for i := 0; i < b.N; i++ { 2700 c := make(chan error) 2701 for j := 0; j < parallel; j++ { 2702 j := j 2703 i := i 2704 go func(pod v1.Pod) { 2705 pod.Name = fmt.Sprintf("post%d-%d-%d-%d", parallel, b.N, j, i) 2706 _, err := client.CoreV1().RESTClient().Post(). 2707 Namespace("default"). 2708 Resource("pods"). 2709 SetHeader("Content-Type", "application/yaml"). 2710 Body(encodePod(pod)).Do(context.TODO()).Get() 2711 c <- err 2712 }(pod) 2713 } 2714 for j := 0; j < parallel; j++ { 2715 err := <-c 2716 if err != nil { 2717 b.Fatal(err) 2718 } 2719 } 2720 close(c) 2721 } 2722 } 2723 } 2724 2725 func createNamespace(client clientset.Interface, name string) error { 2726 namespace := v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}} 2727 namespaceBytes, err := yaml.Marshal(namespace) 2728 if err != nil { 2729 return fmt.Errorf("Failed to marshal namespace: %v", err) 2730 } 2731 _, err = client.CoreV1().RESTClient().Get(). 2732 Resource("namespaces"). 2733 SetHeader("Content-Type", "application/yaml"). 2734 Body(namespaceBytes).Do(context.TODO()).Get() 2735 if err != nil { 2736 return fmt.Errorf("Failed to create namespace: %v", err) 2737 } 2738 return nil 2739 } 2740 2741 func benchListPod(client clientset.Interface, pod v1.Pod, num int) func(*testing.B) { 2742 return func(b *testing.B) { 2743 namespace := fmt.Sprintf("get-%d-%d", num, b.N) 2744 if err := createNamespace(client, namespace); err != nil { 2745 b.Fatal(err) 2746 } 2747 // Create pods 2748 for i := 0; i < num; i++ { 2749 pod.Name = fmt.Sprintf("get-%d-%d", b.N, i) 2750 pod.Namespace = namespace 2751 _, err := client.CoreV1().RESTClient().Post(). 2752 Namespace(namespace). 2753 Resource("pods"). 2754 SetHeader("Content-Type", "application/yaml"). 2755 Body(encodePod(pod)).Do(context.TODO()).Get() 2756 if err != nil { 2757 b.Fatalf("Failed to create object: %v", err) 2758 } 2759 } 2760 2761 b.ResetTimer() 2762 b.ReportAllocs() 2763 for i := 0; i < b.N; i++ { 2764 _, err := client.CoreV1().RESTClient().Get(). 2765 Namespace(namespace). 2766 Resource("pods"). 2767 SetHeader("Accept", "application/vnd.kubernetes.protobuf"). 2768 Do(context.TODO()).Get() 2769 if err != nil { 2770 b.Fatalf("Failed to patch object: %v", err) 2771 } 2772 } 2773 } 2774 } 2775 2776 func benchRepeatedUpdate(client clientset.Interface, podName string) func(*testing.B) { 2777 return func(b *testing.B) { 2778 b.ResetTimer() 2779 b.ReportAllocs() 2780 for i := 0; i < b.N; i++ { 2781 _, err := client.CoreV1().RESTClient().Patch(types.JSONPatchType). 2782 Namespace("default"). 2783 Resource("pods"). 2784 Name(podName). 2785 Body([]byte(fmt.Sprintf(`[{"op": "replace", "path": "/spec/containers/0/image", "value": "image%d"}]`, i))).Do(context.TODO()).Get() 2786 if err != nil { 2787 b.Fatalf("Failed to patch object: %v", err) 2788 } 2789 } 2790 } 2791 } 2792 2793 func TestUpgradeClientSideToServerSideApply(t *testing.T) { 2794 client, closeFn := setup(t) 2795 defer closeFn() 2796 2797 obj := []byte(` 2798 apiVersion: apps/v1 2799 kind: Deployment 2800 metadata: 2801 name: my-deployment 2802 annotations: 2803 "kubectl.kubernetes.io/last-applied-configuration": | 2804 {"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"my-deployment","labels":{"app":"my-app"}},"spec":{"replicas": 3,"template":{"metadata":{"labels":{"app":"my-app"}},"spec":{"containers":[{"name":"my-c","image":"my-image"}]}}}} 2805 labels: 2806 app: my-app 2807 spec: 2808 replicas: 100000 2809 selector: 2810 matchLabels: 2811 app: my-app 2812 template: 2813 metadata: 2814 labels: 2815 app: my-app 2816 spec: 2817 containers: 2818 - name: my-c 2819 image: my-image 2820 `) 2821 2822 deployment, err := yamlutil.ToJSON(obj) 2823 if err != nil { 2824 t.Fatalf("Failed marshal yaml: %v", err) 2825 } 2826 2827 _, err = client.CoreV1().RESTClient().Post(). 2828 AbsPath("/apis/apps/v1"). 2829 Namespace("default"). 2830 Resource("deployments"). 2831 Body(deployment).Do(context.TODO()).Get() 2832 if err != nil { 2833 t.Fatalf("Failed to create object: %v", err) 2834 } 2835 2836 obj = []byte(` 2837 apiVersion: apps/v1 2838 kind: Deployment 2839 metadata: 2840 name: my-deployment 2841 labels: 2842 app: my-new-label 2843 spec: 2844 replicas: 3 # expect conflict 2845 template: 2846 metadata: 2847 labels: 2848 app: my-app 2849 spec: 2850 containers: 2851 - name: my-c 2852 image: my-image 2853 `) 2854 2855 deployment, err = yamlutil.ToJSON(obj) 2856 if err != nil { 2857 t.Fatalf("Failed marshal yaml: %v", err) 2858 } 2859 2860 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2861 AbsPath("/apis/apps/v1"). 2862 Namespace("default"). 2863 Resource("deployments"). 2864 Name("my-deployment"). 2865 Param("fieldManager", "kubectl"). 2866 Body(deployment). 2867 Do(context.TODO()). 2868 Get() 2869 if !apierrors.IsConflict(err) { 2870 t.Fatalf("Expected conflict error but got: %v", err) 2871 } 2872 2873 obj = []byte(` 2874 apiVersion: apps/v1 2875 kind: Deployment 2876 metadata: 2877 name: my-deployment 2878 labels: 2879 app: my-new-label 2880 spec: 2881 template: 2882 metadata: 2883 labels: 2884 app: my-app 2885 spec: 2886 containers: 2887 - name: my-c 2888 image: my-image-new 2889 `) 2890 2891 deployment, err = yamlutil.ToJSON(obj) 2892 if err != nil { 2893 t.Fatalf("Failed marshal yaml: %v", err) 2894 } 2895 2896 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType). 2897 AbsPath("/apis/apps/v1"). 2898 Namespace("default"). 2899 Resource("deployments"). 2900 Name("my-deployment"). 2901 Param("fieldManager", "kubectl"). 2902 Body(deployment). 2903 Do(context.TODO()). 2904 Get() 2905 if err != nil { 2906 t.Fatalf("Failed to apply object: %v", err) 2907 } 2908 2909 deploymentObj, err := client.AppsV1().Deployments("default").Get(context.TODO(), "my-deployment", metav1.GetOptions{}) 2910 if err != nil { 2911 t.Fatalf("Failed to get object: %v", err) 2912 } 2913 if *deploymentObj.Spec.Replicas != 100000 { 2914 t.Fatalf("expected to get obj with replicas %d, but got %d", 100000, *deploymentObj.Spec.Replicas) 2915 } 2916 if deploymentObj.Spec.Template.Spec.Containers[0].Image != "my-image-new" { 2917 t.Fatalf("expected to get obj with image %s, but got %s", "my-image-new", deploymentObj.Spec.Template.Spec.Containers[0].Image) 2918 } 2919 } 2920 2921 func TestRenamingAppliedFieldManagers(t *testing.T) { 2922 client, closeFn := setup(t) 2923 defer closeFn() 2924 2925 // Creating an object 2926 podBytes := []byte(`{ 2927 "apiVersion": "v1", 2928 "kind": "Pod", 2929 "metadata": { 2930 "name": "just-a-pod", 2931 "labels": { 2932 "a": "one" 2933 } 2934 }, 2935 "spec": { 2936 "containers": [{ 2937 "name": "test-container-a", 2938 "image": "test-image-one" 2939 }] 2940 } 2941 }`) 2942 _, err := client.CoreV1().RESTClient(). 2943 Patch(types.ApplyPatchType). 2944 Namespace("default"). 2945 Param("fieldManager", "multi_manager_one"). 2946 Resource("pods"). 2947 Name("just-a-pod"). 2948 Body(podBytes). 2949 Do(context.TODO()). 2950 Get() 2951 if err != nil { 2952 t.Fatalf("Failed to apply: %v", err) 2953 } 2954 _, err = client.CoreV1().RESTClient(). 2955 Patch(types.ApplyPatchType). 2956 Namespace("default"). 2957 Param("fieldManager", "multi_manager_two"). 2958 Resource("pods"). 2959 Name("just-a-pod"). 2960 Body([]byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"labels":{"b":"two"}}}`)). 2961 Do(context.TODO()). 2962 Get() 2963 if err != nil { 2964 t.Fatalf("Failed to apply: %v", err) 2965 } 2966 2967 pod, err := client.CoreV1().Pods("default").Get(context.TODO(), "just-a-pod", metav1.GetOptions{}) 2968 if err != nil { 2969 t.Fatalf("Failed to get object: %v", err) 2970 } 2971 expectedLabels := map[string]string{ 2972 "a": "one", 2973 "b": "two", 2974 } 2975 if !reflect.DeepEqual(pod.Labels, expectedLabels) { 2976 t.Fatalf("Expected labels to be %v, but got %v", expectedLabels, pod.Labels) 2977 } 2978 2979 managedFields := pod.GetManagedFields() 2980 for i := range managedFields { 2981 managedFields[i].Manager = "multi_manager" 2982 } 2983 pod.SetManagedFields(managedFields) 2984 2985 obj, err := client.CoreV1().RESTClient(). 2986 Put(). 2987 Namespace("default"). 2988 Resource("pods"). 2989 Name("just-a-pod"). 2990 Body(pod). 2991 Do(context.TODO()). 2992 Get() 2993 if err != nil { 2994 t.Fatalf("Failed to update: %v", err) 2995 } 2996 2997 accessor, err := meta.Accessor(obj) 2998 if err != nil { 2999 t.Fatalf("Failed to get meta accessor for object: %v", err) 3000 } 3001 managedFields = accessor.GetManagedFields() 3002 if len(managedFields) != 1 { 3003 t.Fatalf("Expected object to have 1 managed fields entry, got: %d", len(managedFields)) 3004 } 3005 entry := managedFields[0] 3006 if entry.Manager != "multi_manager" || entry.Operation != "Apply" || string(entry.FieldsV1.Raw) != `{"f:metadata":{"f:labels":{"f:b":{}}}}` { 3007 t.Fatalf(`Unexpected entry, got: %v`, entry) 3008 } 3009 } 3010 3011 func TestRenamingUpdatedFieldManagers(t *testing.T) { 3012 client, closeFn := setup(t) 3013 defer closeFn() 3014 3015 // Creating an object 3016 podBytes := []byte(`{ 3017 "apiVersion": "v1", 3018 "kind": "Pod", 3019 "metadata": { 3020 "name": "just-a-pod" 3021 }, 3022 "spec": { 3023 "containers": [{ 3024 "name": "test-container-a", 3025 "image": "test-image-one" 3026 }] 3027 } 3028 }`) 3029 _, err := client.CoreV1().RESTClient(). 3030 Patch(types.ApplyPatchType). 3031 Namespace("default"). 3032 Param("fieldManager", "first"). 3033 Resource("pods"). 3034 Name("just-a-pod"). 3035 Body(podBytes). 3036 Do(context.TODO()). 3037 Get() 3038 if err != nil { 3039 t.Fatalf("Failed to create object: %v", err) 3040 } 3041 3042 _, err = client.CoreV1().RESTClient(). 3043 Patch(types.MergePatchType). 3044 Namespace("default"). 3045 Param("fieldManager", "multi_manager_one"). 3046 Resource("pods"). 3047 Name("just-a-pod"). 3048 Body([]byte(`{"metadata":{"labels":{"a":"one"}}}`)). 3049 Do(context.TODO()). 3050 Get() 3051 if err != nil { 3052 t.Fatalf("Failed to update: %v", err) 3053 } 3054 _, err = client.CoreV1().RESTClient(). 3055 Patch(types.MergePatchType). 3056 Namespace("default"). 3057 Param("fieldManager", "multi_manager_two"). 3058 Resource("pods"). 3059 Name("just-a-pod"). 3060 Body([]byte(`{"metadata":{"labels":{"b":"two"}}}`)). 3061 Do(context.TODO()). 3062 Get() 3063 if err != nil { 3064 t.Fatalf("Failed to update: %v", err) 3065 } 3066 3067 pod, err := client.CoreV1().Pods("default").Get(context.TODO(), "just-a-pod", metav1.GetOptions{}) 3068 if err != nil { 3069 t.Fatalf("Failed to get object: %v", err) 3070 } 3071 expectedLabels := map[string]string{ 3072 "a": "one", 3073 "b": "two", 3074 } 3075 if !reflect.DeepEqual(pod.Labels, expectedLabels) { 3076 t.Fatalf("Expected labels to be %v, but got %v", expectedLabels, pod.Labels) 3077 } 3078 3079 managedFields := pod.GetManagedFields() 3080 for i := range managedFields { 3081 managedFields[i].Manager = "multi_manager" 3082 } 3083 pod.SetManagedFields(managedFields) 3084 3085 obj, err := client.CoreV1().RESTClient(). 3086 Put(). 3087 Namespace("default"). 3088 Resource("pods"). 3089 Name("just-a-pod"). 3090 Body(pod). 3091 Do(context.TODO()). 3092 Get() 3093 if err != nil { 3094 t.Fatalf("Failed to update: %v", err) 3095 } 3096 3097 accessor, err := meta.Accessor(obj) 3098 if err != nil { 3099 t.Fatalf("Failed to get meta accessor for object: %v", err) 3100 } 3101 managedFields = accessor.GetManagedFields() 3102 if len(managedFields) != 2 { 3103 t.Fatalf("Expected object to have 2 managed fields entries, got: %d", len(managedFields)) 3104 } 3105 entry := managedFields[1] 3106 if entry.Manager != "multi_manager" || entry.Operation != "Update" || string(entry.FieldsV1.Raw) != `{"f:metadata":{"f:labels":{"f:b":{}}}}` { 3107 t.Fatalf(`Unexpected entry, got: %v`, entry) 3108 } 3109 } 3110 3111 func TestDroppingSubresourceField(t *testing.T) { 3112 client, closeFn := setup(t) 3113 defer closeFn() 3114 3115 // Creating an object 3116 podBytes := []byte(`{ 3117 "apiVersion": "v1", 3118 "kind": "Pod", 3119 "metadata": { 3120 "name": "just-a-pod" 3121 }, 3122 "spec": { 3123 "containers": [{ 3124 "name": "test-container-a", 3125 "image": "test-image-one" 3126 }] 3127 } 3128 }`) 3129 _, err := client.CoreV1().RESTClient(). 3130 Patch(types.ApplyPatchType). 3131 Namespace("default"). 3132 Param("fieldManager", "first"). 3133 Resource("pods"). 3134 Name("just-a-pod"). 3135 Body(podBytes). 3136 Do(context.TODO()). 3137 Get() 3138 if err != nil { 3139 t.Fatalf("Failed to create object: %v", err) 3140 } 3141 3142 _, err = client.CoreV1().RESTClient(). 3143 Patch(types.ApplyPatchType). 3144 Namespace("default"). 3145 Param("fieldManager", "label_manager"). 3146 Resource("pods"). 3147 Name("just-a-pod"). 3148 Body([]byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"labels":{"a":"one"}}}`)). 3149 Do(context.TODO()). 3150 Get() 3151 if err != nil { 3152 t.Fatalf("Failed to apply: %v", err) 3153 } 3154 _, err = client.CoreV1().RESTClient(). 3155 Patch(types.ApplyPatchType). 3156 Namespace("default"). 3157 Param("fieldManager", "label_manager"). 3158 Resource("pods"). 3159 Name("just-a-pod"). 3160 SubResource("status"). 3161 Body([]byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"labels":{"b":"two"}}}`)). 3162 Do(context.TODO()). 3163 Get() 3164 if err != nil { 3165 t.Fatalf("Failed to apply: %v", err) 3166 } 3167 3168 pod, err := client.CoreV1().Pods("default").Get(context.TODO(), "just-a-pod", metav1.GetOptions{}) 3169 if err != nil { 3170 t.Fatalf("Failed to get object: %v", err) 3171 } 3172 expectedLabels := map[string]string{ 3173 "a": "one", 3174 "b": "two", 3175 } 3176 if !reflect.DeepEqual(pod.Labels, expectedLabels) { 3177 t.Fatalf("Expected labels to be %v, but got %v", expectedLabels, pod.Labels) 3178 } 3179 3180 managedFields := pod.GetManagedFields() 3181 if len(managedFields) != 3 { 3182 t.Fatalf("Expected object to have 3 managed fields entries, got: %d", len(managedFields)) 3183 } 3184 if managedFields[1].Manager != "label_manager" || managedFields[1].Operation != "Apply" || managedFields[1].Subresource != "" { 3185 t.Fatalf(`Unexpected entry, got: %v`, managedFields[1]) 3186 } 3187 if managedFields[2].Manager != "label_manager" || managedFields[2].Operation != "Apply" || managedFields[2].Subresource != "status" { 3188 t.Fatalf(`Unexpected entry, got: %v`, managedFields[2]) 3189 } 3190 3191 for i := range managedFields { 3192 managedFields[i].Subresource = "" 3193 } 3194 pod.SetManagedFields(managedFields) 3195 3196 obj, err := client.CoreV1().RESTClient(). 3197 Put(). 3198 Namespace("default"). 3199 Resource("pods"). 3200 Name("just-a-pod"). 3201 Body(pod). 3202 Do(context.TODO()). 3203 Get() 3204 if err != nil { 3205 t.Fatalf("Failed to update: %v", err) 3206 } 3207 3208 accessor, err := meta.Accessor(obj) 3209 if err != nil { 3210 t.Fatalf("Failed to get meta accessor for object: %v", err) 3211 } 3212 managedFields = accessor.GetManagedFields() 3213 if len(managedFields) != 2 { 3214 t.Fatalf("Expected object to have 2 managed fields entries, got: %d", len(managedFields)) 3215 } 3216 entry := managedFields[1] 3217 if entry.Manager != "label_manager" || entry.Operation != "Apply" || string(entry.FieldsV1.Raw) != `{"f:metadata":{"f:labels":{"f:b":{}}}}` { 3218 t.Fatalf(`Unexpected entry, got: %v`, entry) 3219 } 3220 } 3221 3222 func TestDroppingSubresourceFromSpecField(t *testing.T) { 3223 client, closeFn := setup(t) 3224 defer closeFn() 3225 3226 // Creating an object 3227 podBytes := []byte(`{ 3228 "apiVersion": "v1", 3229 "kind": "Pod", 3230 "metadata": { 3231 "name": "just-a-pod" 3232 }, 3233 "spec": { 3234 "containers": [{ 3235 "name": "test-container-a", 3236 "image": "test-image-one" 3237 }] 3238 } 3239 }`) 3240 _, err := client.CoreV1().RESTClient(). 3241 Patch(types.ApplyPatchType). 3242 Namespace("default"). 3243 Param("fieldManager", "first"). 3244 Resource("pods"). 3245 Name("just-a-pod"). 3246 Body(podBytes). 3247 Do(context.TODO()). 3248 Get() 3249 if err != nil { 3250 t.Fatalf("Failed to create object: %v", err) 3251 } 3252 3253 _, err = client.CoreV1().RESTClient(). 3254 Patch(types.MergePatchType). 3255 Namespace("default"). 3256 Param("fieldManager", "manager"). 3257 Resource("pods"). 3258 Name("just-a-pod"). 3259 Body([]byte(`{"metadata":{"labels":{"a":"two"}}}`)). 3260 Do(context.TODO()). 3261 Get() 3262 if err != nil { 3263 t.Fatalf("Failed to update: %v", err) 3264 } 3265 3266 _, err = client.CoreV1().RESTClient(). 3267 Patch(types.MergePatchType). 3268 Namespace("default"). 3269 Param("fieldManager", "manager"). 3270 Resource("pods"). 3271 SubResource("status"). 3272 Name("just-a-pod"). 3273 Body([]byte(`{"status":{"phase":"Running"}}`)). 3274 Do(context.TODO()). 3275 Get() 3276 if err != nil { 3277 t.Fatalf("Failed to apply: %v", err) 3278 } 3279 3280 pod, err := client.CoreV1().Pods("default").Get(context.TODO(), "just-a-pod", metav1.GetOptions{}) 3281 if err != nil { 3282 t.Fatalf("Failed to get object: %v", err) 3283 } 3284 expectedLabels := map[string]string{"a": "two"} 3285 if !reflect.DeepEqual(pod.Labels, expectedLabels) { 3286 t.Fatalf("Expected labels to be %v, but got %v", expectedLabels, pod.Labels) 3287 } 3288 if pod.Status.Phase != v1.PodRunning { 3289 t.Fatalf("Expected phase to be %q, but got %q", v1.PodRunning, pod.Status.Phase) 3290 } 3291 3292 managedFields := pod.GetManagedFields() 3293 if len(managedFields) != 3 { 3294 t.Fatalf("Expected object to have 3 managed fields entries, got: %d", len(managedFields)) 3295 } 3296 if managedFields[1].Manager != "manager" || managedFields[1].Operation != "Update" || managedFields[1].Subresource != "" { 3297 t.Fatalf(`Unexpected entry, got: %v`, managedFields[1]) 3298 } 3299 if managedFields[2].Manager != "manager" || managedFields[2].Operation != "Update" || managedFields[2].Subresource != "status" { 3300 t.Fatalf(`Unexpected entry, got: %v`, managedFields[2]) 3301 } 3302 3303 for i := range managedFields { 3304 managedFields[i].Subresource = "" 3305 } 3306 pod.SetManagedFields(managedFields) 3307 3308 obj, err := client.CoreV1().RESTClient(). 3309 Put(). 3310 Namespace("default"). 3311 Resource("pods"). 3312 Name("just-a-pod"). 3313 Body(pod). 3314 Do(context.TODO()). 3315 Get() 3316 if err != nil { 3317 t.Fatalf("Failed to update: %v", err) 3318 } 3319 3320 accessor, err := meta.Accessor(obj) 3321 if err != nil { 3322 t.Fatalf("Failed to get meta accessor for object: %v", err) 3323 } 3324 managedFields = accessor.GetManagedFields() 3325 if len(managedFields) != 2 { 3326 t.Fatalf("Expected object to have 2 managed fields entries, got: %d", len(managedFields)) 3327 } 3328 entry := managedFields[1] 3329 if entry.Manager != "manager" || entry.Operation != "Update" || string(entry.FieldsV1.Raw) != `{"f:status":{"f:phase":{}}}` { 3330 t.Fatalf(`Unexpected entry, got: %v`, entry) 3331 } 3332 } 3333 3334 func TestSubresourceField(t *testing.T) { 3335 client, closeFn := setup(t) 3336 defer closeFn() 3337 3338 // Creating a deployment 3339 deploymentBytes := []byte(`{ 3340 "apiVersion": "apps/v1", 3341 "kind": "Deployment", 3342 "metadata": { 3343 "name": "deployment", 3344 "labels": {"app": "nginx"} 3345 }, 3346 "spec": { 3347 "replicas": 3, 3348 "selector": { 3349 "matchLabels": { 3350 "app": "nginx" 3351 } 3352 }, 3353 "template": { 3354 "metadata": { 3355 "labels": { 3356 "app": "nginx" 3357 } 3358 }, 3359 "spec": { 3360 "containers": [{ 3361 "name": "nginx", 3362 "image": "nginx:latest" 3363 }] 3364 } 3365 } 3366 } 3367 }`) 3368 _, err := client.CoreV1().RESTClient(). 3369 Patch(types.ApplyPatchType). 3370 AbsPath("/apis/apps/v1"). 3371 Namespace("default"). 3372 Resource("deployments"). 3373 Name("deployment"). 3374 Param("fieldManager", "manager"). 3375 Body(deploymentBytes).Do(context.TODO()).Get() 3376 if err != nil { 3377 t.Fatalf("Failed to apply object: %v", err) 3378 } 3379 3380 _, err = client.CoreV1().RESTClient(). 3381 Patch(types.MergePatchType). 3382 AbsPath("/apis/apps/v1"). 3383 Namespace("default"). 3384 Resource("deployments"). 3385 SubResource("scale"). 3386 Name("deployment"). 3387 Body([]byte(`{"spec":{"replicas":32}}`)). 3388 Param("fieldManager", "manager"). 3389 Do(context.TODO()). 3390 Get() 3391 if err != nil { 3392 t.Fatalf("Failed to update status: %v", err) 3393 } 3394 3395 deployment, err := client.AppsV1().Deployments("default").Get(context.TODO(), "deployment", metav1.GetOptions{}) 3396 if err != nil { 3397 t.Fatalf("Failed to get object: %v", err) 3398 } 3399 3400 managedFields := deployment.GetManagedFields() 3401 if len(managedFields) != 2 { 3402 t.Fatalf("Expected object to have 2 managed fields entries, got: %d", len(managedFields)) 3403 } 3404 if managedFields[0].Manager != "manager" || managedFields[0].Operation != "Apply" || managedFields[0].Subresource != "" { 3405 t.Fatalf(`Unexpected entry, got: %v`, managedFields[0]) 3406 } 3407 if managedFields[1].Manager != "manager" || 3408 managedFields[1].Operation != "Update" || 3409 managedFields[1].Subresource != "scale" || 3410 string(managedFields[1].FieldsV1.Raw) != `{"f:spec":{"f:replicas":{}}}` { 3411 t.Fatalf(`Unexpected entry, got: %v`, managedFields[1]) 3412 } 3413 } 3414 3415 // K8s has a bug introduced in vX.XX.X which changed the treatment of 3416 // ObjectReferences from granular to atomic. This means that only one manager 3417 // may own all fields of the ObjectReference. This resulted in a regression 3418 // for the common use case of user-specified GVK, and machine-populated UID fields. 3419 // 3420 // This is a test to show that clusters affected by this bug before it was fixed 3421 // do not experience any friction when updating to a version of k8s which marks 3422 // the fields' management again as granular. 3423 func TestApplyFormerlyAtomicFields(t *testing.T) { 3424 // Start server with our populated ObjectReference. Since it is atomic its 3425 // ownership changed when XX popualted the UID after the user specified the 3426 // GVKN. 3427 3428 // 1. Create PersistentVolume with its claimRef owned by 3429 // kube-controller-manager as in v1.22 - 1.24 3430 // 2. Attempt to re-apply the original PersistentVolume which does not 3431 // include uid. 3432 // 3. Check that: 3433 // a.) The operaiton was successfu; 3434 // b.) The uid is unchanged 3435 3436 client, closeFn := setup(t) 3437 defer closeFn() 3438 3439 // old PersistentVolume from last version of k8s with its claimRef owned 3440 // atomically 3441 oldPersistentVolume := []byte(` 3442 { 3443 "apiVersion": "v1", 3444 "kind": "PersistentVolume", 3445 "metadata": { 3446 "creationTimestamp": "2022-06-08T23:46:32Z", 3447 "finalizers": [ 3448 "kubernetes.io/pv-protection" 3449 ], 3450 "labels": { 3451 "type": "local" 3452 }, 3453 "name": "pv-storage", 3454 "uid": "112b18f7-fde6-4e48-aa61-f5168bd576b8" 3455 }, 3456 "spec": { 3457 "accessModes": [ 3458 "ReadWriteOnce" 3459 ], 3460 "capacity": { 3461 "storage": "16Mi" 3462 }, 3463 "claimRef": { 3464 "apiVersion": "v1", 3465 "kind": "PersistentVolumeClaim", 3466 "name": "pvc-storage", 3467 "namespace": "default", 3468 "resourceVersion": "15499", 3469 "uid": "2018e302-7b12-406c-9fa2-e52535d29e48" 3470 }, 3471 "hostPath": { 3472 "path": "“/tmp/mydata", 3473 "type": "" 3474 }, 3475 "persistentVolumeReclaimPolicy": "Retain", 3476 "volumeMode": "Filesystem" 3477 }, 3478 "status": { 3479 "phase": "Bound" 3480 } 3481 }`) 3482 3483 managedFieldsUpdate := []byte(`{ 3484 "apiVersion": "v1", 3485 "kind": "PersistentVolume", 3486 "metadata": { 3487 "name": "pv-storage", 3488 "managedFields": [ 3489 { 3490 "apiVersion": "v1", 3491 "fieldsType": "FieldsV1", 3492 "fieldsV1": { 3493 "f:metadata": { 3494 "f:labels": { 3495 "f:type": {} 3496 } 3497 }, 3498 "f:spec": { 3499 "f:accessModes": {}, 3500 "f:capacity": { 3501 "f:storage": {} 3502 }, 3503 "f:hostPath": { 3504 "f:path": {} 3505 }, 3506 "f:storageClassName": {} 3507 } 3508 }, 3509 "manager": "apply_test", 3510 "operation": "Apply", 3511 "time": "2022-06-08T23:46:32Z" 3512 }, 3513 { 3514 "apiVersion": "v1", 3515 "fieldsType": "FieldsV1", 3516 "fieldsV1": { 3517 "f:status": { 3518 "f:phase": {} 3519 } 3520 }, 3521 "manager": "kube-controller-manager", 3522 "operation": "Update", 3523 "subresource": "status", 3524 "time": "2022-06-08T23:46:32Z" 3525 }, 3526 { 3527 "apiVersion": "v1", 3528 "fieldsType": "FieldsV1", 3529 "fieldsV1": { 3530 "f:spec": { 3531 "f:claimRef": {} 3532 } 3533 }, 3534 "manager": "kube-controller-manager", 3535 "operation": "Update", 3536 "time": "2022-06-08T23:46:37Z" 3537 } 3538 ] 3539 } 3540 }`) 3541 3542 // Re-applies name and namespace 3543 originalPV := []byte(`{ 3544 "kind": "PersistentVolume", 3545 "apiVersion": "v1", 3546 "metadata": { 3547 "labels": { 3548 "type": "local" 3549 }, 3550 "name": "pv-storage", 3551 }, 3552 "spec": { 3553 "storageClassName": "", 3554 "capacity": { 3555 "storage": "16Mi" 3556 }, 3557 "accessModes": [ 3558 "ReadWriteOnce" 3559 ], 3560 "hostPath": { 3561 "path": "“/tmp/mydata" 3562 }, 3563 "claimRef": { 3564 "name": "pvc-storage", 3565 "namespace": "default" 3566 } 3567 } 3568 }`) 3569 3570 // Create PV 3571 originalObj, err := client.CoreV1().RESTClient(). 3572 Post(). 3573 Param("fieldManager", "apply_test"). 3574 Resource("persistentvolumes"). 3575 Body(oldPersistentVolume). 3576 Do(context.TODO()). 3577 Get() 3578 3579 if err != nil { 3580 t.Fatalf("Failed to apply object: %v", err) 3581 } else if _, ok := originalObj.(*v1.PersistentVolume); !ok { 3582 t.Fatalf("returned object is incorrect type: %t", originalObj) 3583 } 3584 3585 // Directly set managed fields to object 3586 newObj, err := client.CoreV1().RESTClient(). 3587 Patch(types.StrategicMergePatchType). 3588 Name("pv-storage"). 3589 Param("fieldManager", "apply_test"). 3590 Resource("persistentvolumes"). 3591 Body(managedFieldsUpdate). 3592 Do(context.TODO()). 3593 Get() 3594 3595 if err != nil { 3596 t.Fatalf("Failed to apply object: %v", err) 3597 } else if _, ok := newObj.(*v1.PersistentVolume); !ok { 3598 t.Fatalf("returned object is incorrect type: %t", newObj) 3599 } 3600 3601 // Is initialized, attempt to write to fields underneath 3602 // claimRef ObjectReference. 3603 newObj, err = client.CoreV1().RESTClient(). 3604 Patch(types.ApplyPatchType). 3605 Name("pv-storage"). 3606 Param("fieldManager", "apply_test"). 3607 Resource("persistentvolumes"). 3608 Body(originalPV). 3609 Do(context.TODO()). 3610 Get() 3611 3612 if err != nil { 3613 t.Fatalf("Failed to apply object: %v", err) 3614 } else if _, ok := newObj.(*v1.PersistentVolume); !ok { 3615 t.Fatalf("returned object is incorrect type: %t", newObj) 3616 } 3617 3618 // Test that bug is fixed by showing no error and that uid is not cleared. 3619 if !reflect.DeepEqual(originalObj.(*v1.PersistentVolume).Spec.ClaimRef, newObj.(*v1.PersistentVolume).Spec.ClaimRef) { 3620 t.Fatalf("claimRef changed unexpectedly") 3621 } 3622 3623 // Expect that we know own name/namespace fields 3624 // All other fields unowned 3625 // Make sure apply_test now owns claimRef.UID and that kube-controller-manager owns 3626 // claimRef (but its ownership is not respected due to new granular structType) 3627 managedFields := newObj.(*v1.PersistentVolume).ManagedFields 3628 var expectedManagedFields []metav1.ManagedFieldsEntry 3629 expectedManagedFieldsString := []byte(`[ 3630 { 3631 "apiVersion": "v1", 3632 "fieldsType": "FieldsV1", 3633 "fieldsV1": {"f:metadata":{"f:labels":{"f:type":{}}},"f:spec":{"f:accessModes":{},"f:capacity":{"f:storage":{}},"f:claimRef":{"f:name":{},"f:namespace":{}},"f:hostPath":{"f:path":{}},"f:storageClassName":{}}}, 3634 "manager": "apply_test", 3635 "operation": "Apply", 3636 "time": "2022-06-08T23:46:32Z" 3637 }, 3638 { 3639 "apiVersion": "v1", 3640 "fieldsType": "FieldsV1", 3641 "fieldsV1": {"f:status":{"f:phase":{}}}, 3642 "manager": "kube-controller-manager", 3643 "operation": "Update", 3644 "subresource": "status", 3645 "time": "2022-06-08T23:46:32Z" 3646 }, 3647 { 3648 "apiVersion": "v1", 3649 "fieldsType": "FieldsV1", 3650 "fieldsV1": {"f:spec":{"f:claimRef":{}}}, 3651 "manager": "kube-controller-manager", 3652 "operation": "Update", 3653 "time": "2022-06-08T23:46:37Z" 3654 } 3655 ]`) 3656 3657 err = json.Unmarshal(expectedManagedFieldsString, &expectedManagedFields) 3658 if err != nil { 3659 t.Fatalf("unexpectly failed to decode expected managed fields") 3660 } 3661 3662 // Wipe timestamps before comparison 3663 for i := range expectedManagedFields { 3664 expectedManagedFields[i].Time = nil 3665 } 3666 3667 for i := range managedFields { 3668 managedFields[i].Time = nil 3669 } 3670 3671 if !reflect.DeepEqual(expectedManagedFields, managedFields) { 3672 t.Fatalf("unexpected managed fields: %v", cmp.Diff(expectedManagedFields, managedFields)) 3673 } 3674 } 3675 3676 func TestDuplicatesInAssociativeLists(t *testing.T) { 3677 client, closeFn := setup(t) 3678 defer closeFn() 3679 3680 ds := []byte(`{ 3681 "apiVersion": "apps/v1", 3682 "kind": "DaemonSet", 3683 "metadata": { 3684 "name": "example-daemonset", 3685 "labels": { 3686 "app": "example" 3687 } 3688 }, 3689 "spec": { 3690 "selector": { 3691 "matchLabels": { 3692 "app": "example" 3693 } 3694 }, 3695 "template": { 3696 "metadata": { 3697 "labels": { 3698 "app": "example" 3699 } 3700 }, 3701 "spec": { 3702 "containers": [ 3703 { 3704 "name": "nginx", 3705 "image": "nginx", 3706 "ports": [ 3707 { 3708 "name": "port0", 3709 "containerPort": 1 3710 }, 3711 { 3712 "name": "port1", 3713 "containerPort": 80 3714 }, 3715 { 3716 "name": "port2", 3717 "containerPort": 80 3718 } 3719 ], 3720 "env": [ 3721 { 3722 "name": "ENV0", 3723 "value": "/env0value" 3724 }, 3725 { 3726 "name": "PATH", 3727 "value": "/bin" 3728 }, 3729 { 3730 "name": "PATH", 3731 "value": "$PATH:/usr/bin" 3732 } 3733 ] 3734 } 3735 ] 3736 } 3737 } 3738 } 3739 }`) 3740 // Create the object 3741 obj, err := client.AppsV1().RESTClient(). 3742 Post(). 3743 Namespace("default"). 3744 Param("fieldManager", "create"). 3745 Resource("daemonsets"). 3746 Body(ds). 3747 Do(context.TODO()). 3748 Get() 3749 if err != nil { 3750 t.Fatalf("Failed to create the object: %v", err) 3751 } 3752 daemon := obj.(*appsv1.DaemonSet) 3753 if want, got := 3, len(daemon.Spec.Template.Spec.Containers[0].Env); want != got { 3754 t.Fatalf("Expected %v EnvVars, got %v", want, got) 3755 } 3756 if want, got := 3, len(daemon.Spec.Template.Spec.Containers[0].Ports); want != got { 3757 t.Fatalf("Expected %v Ports, got %v", want, got) 3758 } 3759 3760 expectManagedFields(t, daemon.ManagedFields, ` 3761 [ 3762 { 3763 "manager": "create", 3764 "operation": "Update", 3765 "apiVersion": "apps/v1", 3766 "time": null, 3767 "fieldsType": "FieldsV1", 3768 "fieldsV1": { 3769 "f:metadata": { 3770 "f:annotations": { 3771 ".": {}, 3772 "f:deprecated.daemonset.template.generation": {} 3773 }, 3774 "f:labels": { 3775 ".": {}, 3776 "f:app": {} 3777 } 3778 }, 3779 "f:spec": { 3780 "f:revisionHistoryLimit": {}, 3781 "f:selector": {}, 3782 "f:template": { 3783 "f:metadata": { 3784 "f:labels": { 3785 ".": {}, 3786 "f:app": {} 3787 } 3788 }, 3789 "f:spec": { 3790 "f:containers": { 3791 "k:{\"name\":\"nginx\"}": { 3792 ".": {}, 3793 "f:env": { 3794 ".": {}, 3795 "k:{\"name\":\"ENV0\"}": { 3796 ".": {}, 3797 "f:name": {}, 3798 "f:value": {} 3799 }, 3800 "k:{\"name\":\"PATH\"}": {} 3801 }, 3802 "f:image": {}, 3803 "f:imagePullPolicy": {}, 3804 "f:name": {}, 3805 "f:ports": { 3806 ".": {}, 3807 "k:{\"containerPort\":1,\"protocol\":\"TCP\"}": { 3808 ".": {}, 3809 "f:containerPort": {}, 3810 "f:name": {}, 3811 "f:protocol": {} 3812 }, 3813 "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": {} 3814 }, 3815 "f:resources": {}, 3816 "f:terminationMessagePath": {}, 3817 "f:terminationMessagePolicy": {} 3818 } 3819 }, 3820 "f:dnsPolicy": {}, 3821 "f:restartPolicy": {}, 3822 "f:schedulerName": {}, 3823 "f:securityContext": {}, 3824 "f:terminationGracePeriodSeconds": {} 3825 } 3826 }, 3827 "f:updateStrategy": { 3828 "f:rollingUpdate": { 3829 ".": {}, 3830 "f:maxSurge": {}, 3831 "f:maxUnavailable": {} 3832 }, 3833 "f:type": {} 3834 } 3835 } 3836 } 3837 } 3838 ]`) 3839 3840 // Apply unrelated fields, fieldmanager should be strictly additive. 3841 ds = []byte(` 3842 apiVersion: apps/v1 3843 kind: DaemonSet 3844 metadata: 3845 name: example-daemonset 3846 labels: 3847 app: example 3848 spec: 3849 selector: 3850 matchLabels: 3851 app: example 3852 template: 3853 spec: 3854 containers: 3855 - name: nginx 3856 image: nginx 3857 ports: 3858 - name: port3 3859 containerPort: 443 3860 env: 3861 - name: HOME 3862 value: "/usr/home" 3863 `) 3864 obj, err = client.AppsV1().RESTClient(). 3865 Patch(types.ApplyPatchType). 3866 Namespace("default"). 3867 Name("example-daemonset"). 3868 Param("fieldManager", "apply"). 3869 Resource("daemonsets"). 3870 Body(ds). 3871 Do(context.TODO()). 3872 Get() 3873 if err != nil { 3874 t.Fatalf("Failed to aply the first object: %v", err) 3875 } 3876 daemon = obj.(*appsv1.DaemonSet) 3877 if want, got := 4, len(daemon.Spec.Template.Spec.Containers[0].Env); want != got { 3878 t.Fatalf("Expected %v EnvVars, got %v", want, got) 3879 } 3880 if want, got := 4, len(daemon.Spec.Template.Spec.Containers[0].Ports); want != got { 3881 t.Fatalf("Expected %v Ports, got %v", want, got) 3882 } 3883 3884 expectManagedFields(t, daemon.ManagedFields, ` 3885 [ 3886 { 3887 "manager": "apply", 3888 "operation": "Apply", 3889 "apiVersion": "apps/v1", 3890 "time": null, 3891 "fieldsType": "FieldsV1", 3892 "fieldsV1": { 3893 "f:metadata": { 3894 "f:labels": { 3895 "f:app": {} 3896 } 3897 }, 3898 "f:spec": { 3899 "f:selector": {}, 3900 "f:template": { 3901 "f:spec": { 3902 "f:containers": { 3903 "k:{\"name\":\"nginx\"}": { 3904 ".": {}, 3905 "f:env": { 3906 "k:{\"name\":\"HOME\"}": { 3907 ".": {}, 3908 "f:name": {}, 3909 "f:value": {} 3910 } 3911 }, 3912 "f:image": {}, 3913 "f:name": {}, 3914 "f:ports": { 3915 "k:{\"containerPort\":443,\"protocol\":\"TCP\"}": { 3916 ".": {}, 3917 "f:containerPort": {}, 3918 "f:name": {} 3919 } 3920 } 3921 } 3922 } 3923 } 3924 } 3925 } 3926 } 3927 }, 3928 { 3929 "manager": "create", 3930 "operation": "Update", 3931 "apiVersion": "apps/v1", 3932 "time": null, 3933 "fieldsType": "FieldsV1", 3934 "fieldsV1": { 3935 "f:metadata": { 3936 "f:annotations": { 3937 ".": {}, 3938 "f:deprecated.daemonset.template.generation": {} 3939 }, 3940 "f:labels": { 3941 ".": {}, 3942 "f:app": {} 3943 } 3944 }, 3945 "f:spec": { 3946 "f:revisionHistoryLimit": {}, 3947 "f:selector": {}, 3948 "f:template": { 3949 "f:metadata": { 3950 "f:labels": { 3951 ".": {}, 3952 "f:app": {} 3953 } 3954 }, 3955 "f:spec": { 3956 "f:containers": { 3957 "k:{\"name\":\"nginx\"}": { 3958 ".": {}, 3959 "f:env": { 3960 ".": {}, 3961 "k:{\"name\":\"ENV0\"}": { 3962 ".": {}, 3963 "f:name": {}, 3964 "f:value": {} 3965 }, 3966 "k:{\"name\":\"PATH\"}": {} 3967 }, 3968 "f:image": {}, 3969 "f:imagePullPolicy": {}, 3970 "f:name": {}, 3971 "f:ports": { 3972 ".": {}, 3973 "k:{\"containerPort\":1,\"protocol\":\"TCP\"}": { 3974 ".": {}, 3975 "f:containerPort": {}, 3976 "f:name": {}, 3977 "f:protocol": {} 3978 }, 3979 "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": {} 3980 }, 3981 "f:resources": {}, 3982 "f:terminationMessagePath": {}, 3983 "f:terminationMessagePolicy": {} 3984 } 3985 }, 3986 "f:dnsPolicy": {}, 3987 "f:restartPolicy": {}, 3988 "f:schedulerName": {}, 3989 "f:securityContext": {}, 3990 "f:terminationGracePeriodSeconds": {} 3991 } 3992 }, 3993 "f:updateStrategy": { 3994 "f:rollingUpdate": { 3995 ".": {}, 3996 "f:maxSurge": {}, 3997 "f:maxUnavailable": {} 3998 }, 3999 "f:type": {} 4000 } 4001 } 4002 } 4003 } 4004 ] 4005 `) 4006 4007 // Change name of some ports. 4008 ds = []byte(`{ 4009 "apiVersion": "apps/v1", 4010 "kind": "DaemonSet", 4011 "metadata": { 4012 "name": "example-daemonset", 4013 "labels": { 4014 "app": "example" 4015 } 4016 }, 4017 "spec": { 4018 "selector": { 4019 "matchLabels": { 4020 "app": "example" 4021 } 4022 }, 4023 "template": { 4024 "metadata": { 4025 "labels": { 4026 "app": "example" 4027 } 4028 }, 4029 "spec": { 4030 "containers": [ 4031 { 4032 "name": "nginx", 4033 "image": "nginx", 4034 "ports": [ 4035 { 4036 "name": "port0", 4037 "containerPort": 1 4038 }, 4039 { 4040 "name": "port3", 4041 "containerPort": 443 4042 }, 4043 { 4044 "name": "port4", 4045 "containerPort": 80 4046 }, 4047 { 4048 "name": "port5", 4049 "containerPort": 80 4050 } 4051 ], 4052 "env": [ 4053 { 4054 "name": "ENV0", 4055 "value": "/env0value" 4056 }, 4057 { 4058 "name": "PATH", 4059 "value": "/bin" 4060 }, 4061 { 4062 "name": "PATH", 4063 "value": "$PATH:/usr/bin:/usr/local/bin" 4064 }, 4065 { 4066 "name": "HOME", 4067 "value": "/usr/home" 4068 } 4069 ] 4070 } 4071 ] 4072 } 4073 } 4074 } 4075 }`) 4076 obj, err = client.AppsV1().RESTClient(). 4077 Put(). 4078 Namespace("default"). 4079 Name("example-daemonset"). 4080 Param("fieldManager", "update"). 4081 Resource("daemonsets"). 4082 Body(ds). 4083 Do(context.TODO()). 4084 Get() 4085 if err != nil { 4086 t.Fatalf("Failed to update the object: %v", err) 4087 } 4088 daemon = obj.(*appsv1.DaemonSet) 4089 if want, got := 4, len(daemon.Spec.Template.Spec.Containers[0].Env); want != got { 4090 t.Fatalf("Expected %v EnvVars, got %v", want, got) 4091 } 4092 if want, got := 4, len(daemon.Spec.Template.Spec.Containers[0].Ports); want != got { 4093 t.Fatalf("Expected %v Ports, got %v", want, got) 4094 } 4095 4096 expectManagedFields(t, daemon.ManagedFields, ` 4097 [ 4098 { 4099 "manager": "apply", 4100 "operation": "Apply", 4101 "apiVersion": "apps/v1", 4102 "time": null, 4103 "fieldsType": "FieldsV1", 4104 "fieldsV1": { 4105 "f:metadata": { 4106 "f:labels": { 4107 "f:app": {} 4108 } 4109 }, 4110 "f:spec": { 4111 "f:selector": {}, 4112 "f:template": { 4113 "f:spec": { 4114 "f:containers": { 4115 "k:{\"name\":\"nginx\"}": { 4116 ".": {}, 4117 "f:env": { 4118 "k:{\"name\":\"HOME\"}": { 4119 ".": {}, 4120 "f:name": {}, 4121 "f:value": {} 4122 } 4123 }, 4124 "f:image": {}, 4125 "f:name": {}, 4126 "f:ports": { 4127 "k:{\"containerPort\":443,\"protocol\":\"TCP\"}": { 4128 ".": {}, 4129 "f:containerPort": {}, 4130 "f:name": {} 4131 } 4132 } 4133 } 4134 } 4135 } 4136 } 4137 } 4138 } 4139 }, 4140 { 4141 "manager": "create", 4142 "operation": "Update", 4143 "apiVersion": "apps/v1", 4144 "time": null, 4145 "fieldsType": "FieldsV1", 4146 "fieldsV1": { 4147 "f:metadata": { 4148 "f:annotations": {}, 4149 "f:labels": { 4150 ".": {}, 4151 "f:app": {} 4152 } 4153 }, 4154 "f:spec": { 4155 "f:revisionHistoryLimit": {}, 4156 "f:selector": {}, 4157 "f:template": { 4158 "f:metadata": { 4159 "f:labels": { 4160 ".": {}, 4161 "f:app": {} 4162 } 4163 }, 4164 "f:spec": { 4165 "f:containers": { 4166 "k:{\"name\":\"nginx\"}": { 4167 ".": {}, 4168 "f:env": { 4169 ".": {}, 4170 "k:{\"name\":\"ENV0\"}": { 4171 ".": {}, 4172 "f:name": {}, 4173 "f:value": {} 4174 } 4175 }, 4176 "f:image": {}, 4177 "f:imagePullPolicy": {}, 4178 "f:name": {}, 4179 "f:ports": { 4180 ".": {}, 4181 "k:{\"containerPort\":1,\"protocol\":\"TCP\"}": { 4182 ".": {}, 4183 "f:containerPort": {}, 4184 "f:name": {}, 4185 "f:protocol": {} 4186 } 4187 }, 4188 "f:resources": {}, 4189 "f:terminationMessagePath": {}, 4190 "f:terminationMessagePolicy": {} 4191 } 4192 }, 4193 "f:dnsPolicy": {}, 4194 "f:restartPolicy": {}, 4195 "f:schedulerName": {}, 4196 "f:securityContext": {}, 4197 "f:terminationGracePeriodSeconds": {} 4198 } 4199 }, 4200 "f:updateStrategy": { 4201 "f:rollingUpdate": { 4202 ".": {}, 4203 "f:maxSurge": {}, 4204 "f:maxUnavailable": {} 4205 }, 4206 "f:type": {} 4207 } 4208 } 4209 } 4210 }, 4211 { 4212 "manager": "update", 4213 "operation": "Update", 4214 "apiVersion": "apps/v1", 4215 "time": null, 4216 "fieldsType": "FieldsV1", 4217 "fieldsV1": { 4218 "f:metadata": { 4219 "f:annotations": { 4220 "f:deprecated.daemonset.template.generation": {} 4221 } 4222 }, 4223 "f:spec": { 4224 "f:template": { 4225 "f:spec": { 4226 "f:containers": { 4227 "k:{\"name\":\"nginx\"}": { 4228 "f:env": { 4229 "k:{\"name\":\"PATH\"}": {} 4230 }, 4231 "f:ports": { 4232 "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": {} 4233 } 4234 } 4235 } 4236 } 4237 } 4238 } 4239 } 4240 } 4241 ] 4242 `) 4243 4244 // Replaces envvars and paths. 4245 ds = []byte(` 4246 apiVersion: apps/v1 4247 kind: DaemonSet 4248 metadata: 4249 name: example-daemonset 4250 labels: 4251 app: example 4252 spec: 4253 selector: 4254 matchLabels: 4255 app: example 4256 template: 4257 spec: 4258 containers: 4259 - name: nginx 4260 image: nginx 4261 ports: 4262 - name: port80 4263 containerPort: 80 4264 env: 4265 - name: PATH 4266 value: "/bin:/usr/bin:/usr/local/bin" 4267 `) 4268 obj, err = client.AppsV1().RESTClient(). 4269 Patch(types.ApplyPatchType). 4270 Namespace("default"). 4271 Name("example-daemonset"). 4272 Param("fieldManager", "apply"). 4273 Param("force", "true"). 4274 Resource("daemonsets"). 4275 Body(ds). 4276 Do(context.TODO()). 4277 Get() 4278 if err != nil { 4279 t.Fatalf("Failed to apply the second object: %v", err) 4280 } 4281 4282 daemon = obj.(*appsv1.DaemonSet) 4283 // HOME is removed, PATH is replaced with 1. 4284 if want, got := 2, len(daemon.Spec.Template.Spec.Containers[0].Env); want != got { 4285 t.Fatalf("Expected %v EnvVars, got %v", want, got) 4286 } 4287 if want, got := 2, len(daemon.Spec.Template.Spec.Containers[0].Ports); want != got { 4288 t.Fatalf("Expected %v Ports, got %v", want, got) 4289 } 4290 4291 expectManagedFields(t, daemon.ManagedFields, ` 4292 [ 4293 { 4294 "manager": "apply", 4295 "operation": "Apply", 4296 "apiVersion": "apps/v1", 4297 "time": null, 4298 "fieldsType": "FieldsV1", 4299 "fieldsV1": { 4300 "f:metadata": { 4301 "f:labels": { 4302 "f:app": {} 4303 } 4304 }, 4305 "f:spec": { 4306 "f:selector": {}, 4307 "f:template": { 4308 "f:spec": { 4309 "f:containers": { 4310 "k:{\"name\":\"nginx\"}": { 4311 ".": {}, 4312 "f:env": { 4313 "k:{\"name\":\"PATH\"}": { 4314 ".": {}, 4315 "f:name": {}, 4316 "f:value": {} 4317 } 4318 }, 4319 "f:image": {}, 4320 "f:name": {}, 4321 "f:ports": { 4322 "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { 4323 ".": {}, 4324 "f:containerPort": {}, 4325 "f:name": {} 4326 } 4327 } 4328 } 4329 } 4330 } 4331 } 4332 } 4333 } 4334 }, 4335 { 4336 "manager": "create", 4337 "operation": "Update", 4338 "apiVersion": "apps/v1", 4339 "time": null, 4340 "fieldsType": "FieldsV1", 4341 "fieldsV1": { 4342 "f:metadata": { 4343 "f:annotations": {}, 4344 "f:labels": { 4345 ".": {}, 4346 "f:app": {} 4347 } 4348 }, 4349 "f:spec": { 4350 "f:revisionHistoryLimit": {}, 4351 "f:selector": {}, 4352 "f:template": { 4353 "f:metadata": { 4354 "f:labels": { 4355 ".": {}, 4356 "f:app": {} 4357 } 4358 }, 4359 "f:spec": { 4360 "f:containers": { 4361 "k:{\"name\":\"nginx\"}": { 4362 ".": {}, 4363 "f:env": { 4364 ".": {}, 4365 "k:{\"name\":\"ENV0\"}": { 4366 ".": {}, 4367 "f:name": {}, 4368 "f:value": {} 4369 } 4370 }, 4371 "f:image": {}, 4372 "f:imagePullPolicy": {}, 4373 "f:name": {}, 4374 "f:ports": { 4375 ".": {}, 4376 "k:{\"containerPort\":1,\"protocol\":\"TCP\"}": { 4377 ".": {}, 4378 "f:containerPort": {}, 4379 "f:name": {}, 4380 "f:protocol": {} 4381 } 4382 }, 4383 "f:resources": {}, 4384 "f:terminationMessagePath": {}, 4385 "f:terminationMessagePolicy": {} 4386 } 4387 }, 4388 "f:dnsPolicy": {}, 4389 "f:restartPolicy": {}, 4390 "f:schedulerName": {}, 4391 "f:securityContext": {}, 4392 "f:terminationGracePeriodSeconds": {} 4393 } 4394 }, 4395 "f:updateStrategy": { 4396 "f:rollingUpdate": { 4397 ".": {}, 4398 "f:maxSurge": {}, 4399 "f:maxUnavailable": {} 4400 }, 4401 "f:type": {} 4402 } 4403 } 4404 } 4405 }, 4406 { 4407 "manager": "update", 4408 "operation": "Update", 4409 "apiVersion": "apps/v1", 4410 "time": null, 4411 "fieldsType": "FieldsV1", 4412 "fieldsV1": { 4413 "f:metadata": { 4414 "f:annotations": { 4415 "f:deprecated.daemonset.template.generation": {} 4416 } 4417 } 4418 } 4419 } 4420 ] 4421 `) 4422 } 4423 4424 func expectManagedFields(t *testing.T, managedFields []metav1.ManagedFieldsEntry, expect string) { 4425 t.Helper() 4426 for i := range managedFields { 4427 managedFields[i].Time = &metav1.Time{} 4428 } 4429 got, err := json.MarshalIndent(managedFields, "", " ") 4430 if err != nil { 4431 t.Fatalf("Failed to marshal managed fields: %v", err) 4432 } 4433 b := &bytes.Buffer{} 4434 err = json.Indent(b, []byte(expect), "", " ") 4435 if err != nil { 4436 t.Fatalf("Failed to indent json: %v", err) 4437 } 4438 want := b.String() 4439 diff := cmp.Diff(strings.Split(strings.TrimSpace(string(got)), "\n"), strings.Split(strings.TrimSpace(want), "\n")) 4440 if len(diff) > 0 { 4441 t.Fatalf("Want:\n%s\nGot:\n%s\nDiff:\n%s", string(want), string(got), diff) 4442 } 4443 }