sigs.k8s.io/controller-runtime@v0.18.2/pkg/controller/controllerutil/controllerutil_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 controllerutil_test 18 19 import ( 20 "context" 21 "fmt" 22 "math/rand" 23 24 . "github.com/onsi/ginkgo/v2" 25 . "github.com/onsi/gomega" 26 appsv1 "k8s.io/api/apps/v1" 27 corev1 "k8s.io/api/core/v1" 28 extensionsv1beta1 "k8s.io/api/extensions/v1beta1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/client-go/kubernetes/scheme" 33 "k8s.io/utils/ptr" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 36 ) 37 38 var _ = Describe("Controllerutil", func() { 39 Describe("SetOwnerReference", func() { 40 It("should set ownerRef on an empty list", func() { 41 rs := &appsv1.ReplicaSet{} 42 dep := &extensionsv1beta1.Deployment{ 43 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, 44 } 45 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 46 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ 47 Name: "foo", 48 Kind: "Deployment", 49 APIVersion: "extensions/v1beta1", 50 UID: "foo-uid", 51 })) 52 }) 53 54 It("should not duplicate owner references", func() { 55 rs := &appsv1.ReplicaSet{ 56 ObjectMeta: metav1.ObjectMeta{ 57 OwnerReferences: []metav1.OwnerReference{ 58 { 59 Name: "foo", 60 Kind: "Deployment", 61 APIVersion: "extensions/v1beta1", 62 UID: "foo-uid", 63 }, 64 }, 65 }, 66 } 67 dep := &extensionsv1beta1.Deployment{ 68 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, 69 } 70 71 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 72 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ 73 Name: "foo", 74 Kind: "Deployment", 75 APIVersion: "extensions/v1beta1", 76 UID: "foo-uid", 77 })) 78 }) 79 80 It("should update the reference", func() { 81 rs := &appsv1.ReplicaSet{ 82 ObjectMeta: metav1.ObjectMeta{ 83 OwnerReferences: []metav1.OwnerReference{ 84 { 85 Name: "foo", 86 Kind: "Deployment", 87 APIVersion: "extensions/v1alpha1", 88 UID: "foo-uid-1", 89 }, 90 }, 91 }, 92 } 93 dep := &extensionsv1beta1.Deployment{ 94 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 95 } 96 97 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 98 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ 99 Name: "foo", 100 Kind: "Deployment", 101 APIVersion: "extensions/v1beta1", 102 UID: "foo-uid-2", 103 })) 104 }) 105 It("should remove the owner reference", func() { 106 rs := &appsv1.ReplicaSet{ 107 ObjectMeta: metav1.ObjectMeta{ 108 OwnerReferences: []metav1.OwnerReference{ 109 { 110 Name: "foo", 111 Kind: "Deployment", 112 APIVersion: "extensions/v1alpha1", 113 UID: "foo-uid-1", 114 }, 115 }, 116 }, 117 } 118 dep := &extensionsv1beta1.Deployment{ 119 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 120 } 121 122 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 123 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ 124 Name: "foo", 125 Kind: "Deployment", 126 APIVersion: "extensions/v1beta1", 127 UID: "foo-uid-2", 128 })) 129 Expect(controllerutil.RemoveOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 130 Expect(rs.GetOwnerReferences()).To(BeEmpty()) 131 }) 132 It("should remove the owner reference established by the SetControllerReference function", func() { 133 rs := &appsv1.ReplicaSet{} 134 dep := &extensionsv1beta1.Deployment{ 135 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, 136 } 137 138 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) 139 t := true 140 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ 141 Name: "foo", 142 Kind: "Deployment", 143 APIVersion: "extensions/v1beta1", 144 UID: "foo-uid", 145 Controller: &t, 146 BlockOwnerDeletion: &t, 147 })) 148 Expect(controllerutil.RemoveOwnerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) 149 Expect(rs.GetOwnerReferences()).To(BeEmpty()) 150 }) 151 It("should error when trying to remove the reference that doesn't exist", func() { 152 rs := &appsv1.ReplicaSet{ 153 ObjectMeta: metav1.ObjectMeta{}, 154 } 155 dep := &extensionsv1beta1.Deployment{ 156 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 157 } 158 Expect(controllerutil.RemoveOwnerReference(dep, rs, scheme.Scheme)).To(HaveOccurred()) 159 Expect(rs.GetOwnerReferences()).To(BeEmpty()) 160 }) 161 It("should error when trying to remove the reference that doesn't abide by the scheme", func() { 162 rs := &appsv1.ReplicaSet{ 163 ObjectMeta: metav1.ObjectMeta{}, 164 } 165 dep := &extensionsv1beta1.Deployment{ 166 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 167 } 168 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 169 Expect(controllerutil.RemoveOwnerReference(dep, rs, runtime.NewScheme())).To(HaveOccurred()) 170 Expect(rs.GetOwnerReferences()).To(HaveLen(1)) 171 }) 172 It("should error when trying to remove the owner when setting the owner as a non runtime.Object", func() { 173 var obj metav1.Object 174 rs := &appsv1.ReplicaSet{ 175 ObjectMeta: metav1.ObjectMeta{}, 176 } 177 dep := &extensionsv1beta1.Deployment{ 178 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 179 } 180 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 181 Expect(controllerutil.RemoveOwnerReference(obj, rs, scheme.Scheme)).To(HaveOccurred()) 182 Expect(rs.GetOwnerReferences()).To(HaveLen(1)) 183 }) 184 185 It("should error when trying to remove an owner that doesn't exist", func() { 186 rs := &appsv1.ReplicaSet{ 187 ObjectMeta: metav1.ObjectMeta{}, 188 } 189 dep := &extensionsv1beta1.Deployment{ 190 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 191 } 192 dep2 := &extensionsv1beta1.Deployment{ 193 ObjectMeta: metav1.ObjectMeta{Name: "bar", UID: "bar-uid-3"}, 194 } 195 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 196 Expect(controllerutil.RemoveOwnerReference(dep2, rs, scheme.Scheme)).To(HaveOccurred()) 197 Expect(rs.GetOwnerReferences()).To(HaveLen(1)) 198 }) 199 200 It("should return true when HasControllerReference evaluates owner reference set by SetControllerReference", func() { 201 rs := &appsv1.ReplicaSet{ 202 ObjectMeta: metav1.ObjectMeta{}, 203 } 204 dep := &extensionsv1beta1.Deployment{ 205 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 206 } 207 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 208 Expect(controllerutil.HasControllerReference(rs)).To(BeTrue()) 209 }) 210 211 It("should return false when HasControllerReference evaluates owner reference set by SetOwnerReference", func() { 212 rs := &appsv1.ReplicaSet{ 213 ObjectMeta: metav1.ObjectMeta{}, 214 } 215 dep := &extensionsv1beta1.Deployment{ 216 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 217 } 218 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 219 Expect(controllerutil.HasControllerReference(rs)).To(BeFalse()) 220 }) 221 222 It("should error when RemoveControllerReference owner's controller is set to false", func() { 223 rs := &appsv1.ReplicaSet{ 224 ObjectMeta: metav1.ObjectMeta{}, 225 } 226 dep := &extensionsv1beta1.Deployment{ 227 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 228 } 229 Expect(controllerutil.SetOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 230 Expect(controllerutil.RemoveControllerReference(dep, rs, scheme.Scheme)).To(HaveOccurred()) 231 }) 232 233 It("should error when RemoveControllerReference passed in owner is not the owner", func() { 234 rs := &appsv1.ReplicaSet{ 235 ObjectMeta: metav1.ObjectMeta{}, 236 } 237 dep := &extensionsv1beta1.Deployment{ 238 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 239 } 240 dep2 := &extensionsv1beta1.Deployment{ 241 ObjectMeta: metav1.ObjectMeta{Name: "foo-2", UID: "foo-uid-42"}, 242 } 243 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 244 Expect(controllerutil.SetOwnerReference(dep2, rs, scheme.Scheme)).ToNot(HaveOccurred()) 245 Expect(controllerutil.RemoveControllerReference(dep2, rs, scheme.Scheme)).To(HaveOccurred()) 246 Expect(rs.GetOwnerReferences()).To(HaveLen(2)) 247 }) 248 249 It("should not error when RemoveControllerReference owner's controller is set to true", func() { 250 rs := &appsv1.ReplicaSet{ 251 ObjectMeta: metav1.ObjectMeta{}, 252 } 253 dep := &extensionsv1beta1.Deployment{ 254 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid-2"}, 255 } 256 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 257 Expect(controllerutil.RemoveControllerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred()) 258 Expect(rs.GetOwnerReferences()).To(BeEmpty()) 259 }) 260 }) 261 262 Describe("SetControllerReference", func() { 263 It("should set the OwnerReference if it can find the group version kind", func() { 264 rs := &appsv1.ReplicaSet{} 265 dep := &extensionsv1beta1.Deployment{ 266 ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, 267 } 268 269 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) 270 t := true 271 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ 272 Name: "foo", 273 Kind: "Deployment", 274 APIVersion: "extensions/v1beta1", 275 UID: "foo-uid", 276 Controller: &t, 277 BlockOwnerDeletion: &t, 278 })) 279 }) 280 281 It("should return an error if it can't find the group version kind of the owner", func() { 282 rs := &appsv1.ReplicaSet{} 283 dep := &extensionsv1beta1.Deployment{ 284 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 285 } 286 Expect(controllerutil.SetControllerReference(dep, rs, runtime.NewScheme())).To(HaveOccurred()) 287 }) 288 289 It("should return an error if the owner isn't a runtime.Object", func() { 290 rs := &appsv1.ReplicaSet{} 291 Expect(controllerutil.SetControllerReference(&errMetaObj{}, rs, scheme.Scheme)).To(HaveOccurred()) 292 }) 293 294 It("should return an error if object is already owned by another controller", func() { 295 t := true 296 rsOwners := []metav1.OwnerReference{ 297 { 298 Name: "bar", 299 Kind: "Deployment", 300 APIVersion: "extensions/v1beta1", 301 UID: "bar-uid", 302 Controller: &t, 303 BlockOwnerDeletion: &t, 304 }, 305 } 306 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}} 307 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}} 308 309 err := controllerutil.SetControllerReference(dep, rs, scheme.Scheme) 310 311 Expect(err).To(HaveOccurred()) 312 Expect(err).To(BeAssignableToTypeOf(&controllerutil.AlreadyOwnedError{})) 313 }) 314 315 It("should not duplicate existing owner reference", func() { 316 f := false 317 t := true 318 rsOwners := []metav1.OwnerReference{ 319 { 320 Name: "foo", 321 Kind: "Deployment", 322 APIVersion: "extensions/v1beta1", 323 UID: "foo-uid", 324 Controller: &f, 325 BlockOwnerDeletion: &t, 326 }, 327 } 328 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}} 329 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}} 330 331 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) 332 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ 333 Name: "foo", 334 Kind: "Deployment", 335 APIVersion: "extensions/v1beta1", 336 UID: "foo-uid", 337 Controller: &t, 338 BlockOwnerDeletion: &t, 339 })) 340 }) 341 342 It("should replace the owner reference if it's already present", func() { 343 t := true 344 rsOwners := []metav1.OwnerReference{ 345 { 346 Name: "foo", 347 Kind: "Deployment", 348 APIVersion: "extensions/v1alpha1", 349 UID: "foo-uid", 350 Controller: &t, 351 BlockOwnerDeletion: &t, 352 }, 353 } 354 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}} 355 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}} 356 357 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) 358 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ 359 Name: "foo", 360 Kind: "Deployment", 361 APIVersion: "extensions/v1beta1", 362 UID: "foo-uid", 363 Controller: &t, 364 BlockOwnerDeletion: &t, 365 })) 366 }) 367 368 It("should return an error if it's setting a cross-namespace owner reference", func() { 369 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "namespace1"}} 370 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "namespace2", UID: "foo-uid"}} 371 372 err := controllerutil.SetControllerReference(dep, rs, scheme.Scheme) 373 374 Expect(err).To(HaveOccurred()) 375 }) 376 377 It("should return an error if it's owner is namespaced resource but dependant is cluster-scoped resource", func() { 378 pv := &corev1.PersistentVolume{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} 379 pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}} 380 381 err := controllerutil.SetControllerReference(pod, pv, scheme.Scheme) 382 383 Expect(err).To(HaveOccurred()) 384 }) 385 386 It("should not return any error if the existing owner has a different version", func() { 387 f := false 388 t := true 389 rsOwners := []metav1.OwnerReference{ 390 { 391 Name: "foo", 392 Kind: "Deployment", 393 APIVersion: "extensions/v1alpha1", 394 UID: "foo-uid", 395 Controller: &f, 396 BlockOwnerDeletion: &t, 397 }, 398 } 399 rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}} 400 dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}} 401 402 Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) 403 Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ 404 Name: "foo", 405 Kind: "Deployment", 406 // APIVersion is the new owner's one 407 APIVersion: "extensions/v1beta1", 408 UID: "foo-uid", 409 Controller: &t, 410 BlockOwnerDeletion: &t, 411 })) 412 }) 413 }) 414 415 Describe("CreateOrUpdate", func() { 416 var deploy *appsv1.Deployment 417 var deplSpec appsv1.DeploymentSpec 418 var deplKey types.NamespacedName 419 var specr controllerutil.MutateFn 420 421 BeforeEach(func() { 422 deploy = &appsv1.Deployment{ 423 ObjectMeta: metav1.ObjectMeta{ 424 Name: fmt.Sprintf("deploy-%d", rand.Int31()), //nolint:gosec 425 Namespace: "default", 426 }, 427 } 428 429 deplSpec = appsv1.DeploymentSpec{ 430 Selector: &metav1.LabelSelector{ 431 MatchLabels: map[string]string{"foo": "bar"}, 432 }, 433 Template: corev1.PodTemplateSpec{ 434 ObjectMeta: metav1.ObjectMeta{ 435 Labels: map[string]string{ 436 "foo": "bar", 437 }, 438 }, 439 Spec: corev1.PodSpec{ 440 Containers: []corev1.Container{ 441 { 442 Name: "busybox", 443 Image: "busybox", 444 }, 445 }, 446 }, 447 }, 448 } 449 450 deplKey = types.NamespacedName{ 451 Name: deploy.Name, 452 Namespace: deploy.Namespace, 453 } 454 455 specr = deploymentSpecr(deploy, deplSpec) 456 }) 457 458 It("creates a new object if one doesn't exists", func() { 459 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr) 460 461 By("returning no error") 462 Expect(err).NotTo(HaveOccurred()) 463 464 By("returning OperationResultCreated") 465 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 466 467 By("actually having the deployment created") 468 fetched := &appsv1.Deployment{} 469 Expect(c.Get(context.TODO(), deplKey, fetched)).To(Succeed()) 470 471 By("being mutated by MutateFn") 472 Expect(fetched.Spec.Template.Spec.Containers).To(HaveLen(1)) 473 Expect(fetched.Spec.Template.Spec.Containers[0].Name).To(Equal(deplSpec.Template.Spec.Containers[0].Name)) 474 Expect(fetched.Spec.Template.Spec.Containers[0].Image).To(Equal(deplSpec.Template.Spec.Containers[0].Image)) 475 }) 476 477 It("updates existing object", func() { 478 var scale int32 = 2 479 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr) 480 Expect(err).NotTo(HaveOccurred()) 481 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 482 483 op, err = controllerutil.CreateOrUpdate(context.TODO(), c, deploy, deploymentScaler(deploy, scale)) 484 By("returning no error") 485 Expect(err).NotTo(HaveOccurred()) 486 487 By("returning OperationResultUpdated") 488 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdated)) 489 490 By("actually having the deployment scaled") 491 fetched := &appsv1.Deployment{} 492 Expect(c.Get(context.TODO(), deplKey, fetched)).To(Succeed()) 493 Expect(*fetched.Spec.Replicas).To(Equal(scale)) 494 }) 495 496 It("updates only changed objects", func() { 497 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr) 498 499 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 500 Expect(err).NotTo(HaveOccurred()) 501 502 op, err = controllerutil.CreateOrUpdate(context.TODO(), c, deploy, deploymentIdentity) 503 By("returning no error") 504 Expect(err).NotTo(HaveOccurred()) 505 506 By("returning OperationResultNone") 507 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 508 }) 509 510 It("errors when MutateFn changes object name on creation", func() { 511 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, func() error { 512 Expect(specr()).To(Succeed()) 513 return deploymentRenamer(deploy)() 514 }) 515 516 By("returning error") 517 Expect(err).To(HaveOccurred()) 518 519 By("returning OperationResultNone") 520 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 521 }) 522 523 It("errors when MutateFn renames an object", func() { 524 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr) 525 526 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 527 Expect(err).NotTo(HaveOccurred()) 528 529 op, err = controllerutil.CreateOrUpdate(context.TODO(), c, deploy, deploymentRenamer(deploy)) 530 531 By("returning error") 532 Expect(err).To(HaveOccurred()) 533 534 By("returning OperationResultNone") 535 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 536 }) 537 538 It("errors when object namespace changes", func() { 539 op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr) 540 541 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 542 Expect(err).NotTo(HaveOccurred()) 543 544 op, err = controllerutil.CreateOrUpdate(context.TODO(), c, deploy, deploymentNamespaceChanger(deploy)) 545 546 By("returning error") 547 Expect(err).To(HaveOccurred()) 548 549 By("returning OperationResultNone") 550 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 551 }) 552 553 It("aborts immediately if there was an error initially retrieving the object", func() { 554 op, err := controllerutil.CreateOrUpdate(context.TODO(), errorReader{c}, deploy, func() error { 555 Fail("Mutation method should not run") 556 return nil 557 }) 558 559 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 560 Expect(err).To(HaveOccurred()) 561 }) 562 }) 563 564 Describe("CreateOrPatch", func() { 565 var deploy *appsv1.Deployment 566 var deplSpec appsv1.DeploymentSpec 567 var deplKey types.NamespacedName 568 var specr controllerutil.MutateFn 569 570 BeforeEach(func() { 571 deploy = &appsv1.Deployment{ 572 ObjectMeta: metav1.ObjectMeta{ 573 Name: fmt.Sprintf("deploy-%d", rand.Int31()), //nolint:gosec 574 Namespace: "default", 575 }, 576 } 577 578 deplSpec = appsv1.DeploymentSpec{ 579 Selector: &metav1.LabelSelector{ 580 MatchLabels: map[string]string{"foo": "bar"}, 581 }, 582 Template: corev1.PodTemplateSpec{ 583 ObjectMeta: metav1.ObjectMeta{ 584 Labels: map[string]string{ 585 "foo": "bar", 586 }, 587 }, 588 Spec: corev1.PodSpec{ 589 Containers: []corev1.Container{ 590 { 591 Name: "busybox", 592 Image: "busybox", 593 }, 594 }, 595 }, 596 }, 597 } 598 599 deplKey = types.NamespacedName{ 600 Name: deploy.Name, 601 Namespace: deploy.Namespace, 602 } 603 604 specr = deploymentSpecr(deploy, deplSpec) 605 }) 606 607 assertLocalDeployWasUpdated := func(fetched *appsv1.Deployment) { 608 By("local deploy object was updated during patch & has same spec, status, resource version as fetched") 609 if fetched == nil { 610 fetched = &appsv1.Deployment{} 611 ExpectWithOffset(1, c.Get(context.TODO(), deplKey, fetched)).To(Succeed()) 612 } 613 ExpectWithOffset(1, fetched.ResourceVersion).To(Equal(deploy.ResourceVersion)) 614 ExpectWithOffset(1, fetched.Spec).To(BeEquivalentTo(deploy.Spec)) 615 ExpectWithOffset(1, fetched.Status).To(BeEquivalentTo(deploy.Status)) 616 } 617 618 assertLocalDeployStatusWasUpdated := func(fetched *appsv1.Deployment) { 619 By("local deploy object was updated during patch & has same spec, status, resource version as fetched") 620 if fetched == nil { 621 fetched = &appsv1.Deployment{} 622 ExpectWithOffset(1, c.Get(context.TODO(), deplKey, fetched)).To(Succeed()) 623 } 624 ExpectWithOffset(1, fetched.ResourceVersion).To(Equal(deploy.ResourceVersion)) 625 ExpectWithOffset(1, *fetched.Spec.Replicas).To(BeEquivalentTo(int32(5))) 626 ExpectWithOffset(1, fetched.Status).To(BeEquivalentTo(deploy.Status)) 627 ExpectWithOffset(1, len(fetched.Status.Conditions)).To(BeEquivalentTo(1)) 628 } 629 630 It("creates a new object if one doesn't exists", func() { 631 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr) 632 633 By("returning no error") 634 Expect(err).NotTo(HaveOccurred()) 635 636 By("returning OperationResultCreated") 637 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 638 639 By("actually having the deployment created") 640 fetched := &appsv1.Deployment{} 641 Expect(c.Get(context.TODO(), deplKey, fetched)).To(Succeed()) 642 643 By("being mutated by MutateFn") 644 Expect(fetched.Spec.Template.Spec.Containers).To(HaveLen(1)) 645 Expect(fetched.Spec.Template.Spec.Containers[0].Name).To(Equal(deplSpec.Template.Spec.Containers[0].Name)) 646 Expect(fetched.Spec.Template.Spec.Containers[0].Image).To(Equal(deplSpec.Template.Spec.Containers[0].Image)) 647 }) 648 649 It("patches existing object", func() { 650 var scale int32 = 2 651 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr) 652 Expect(err).NotTo(HaveOccurred()) 653 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 654 655 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentScaler(deploy, scale)) 656 By("returning no error") 657 Expect(err).NotTo(HaveOccurred()) 658 659 By("returning OperationResultUpdated") 660 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdated)) 661 662 By("actually having the deployment scaled") 663 fetched := &appsv1.Deployment{} 664 Expect(c.Get(context.TODO(), deplKey, fetched)).To(Succeed()) 665 Expect(*fetched.Spec.Replicas).To(Equal(scale)) 666 assertLocalDeployWasUpdated(fetched) 667 }) 668 669 It("patches only changed objects", func() { 670 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr) 671 672 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 673 Expect(err).NotTo(HaveOccurred()) 674 675 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentIdentity) 676 By("returning no error") 677 Expect(err).NotTo(HaveOccurred()) 678 679 By("returning OperationResultNone") 680 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 681 682 assertLocalDeployWasUpdated(nil) 683 }) 684 685 It("patches only changed status", func() { 686 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr) 687 688 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 689 Expect(err).NotTo(HaveOccurred()) 690 691 deployStatus := appsv1.DeploymentStatus{ 692 ReadyReplicas: 1, 693 Replicas: 3, 694 } 695 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentStatusr(deploy, deployStatus)) 696 By("returning no error") 697 Expect(err).NotTo(HaveOccurred()) 698 699 By("returning OperationResultUpdatedStatusOnly") 700 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdatedStatusOnly)) 701 702 assertLocalDeployWasUpdated(nil) 703 }) 704 705 It("patches resource and status", func() { 706 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr) 707 708 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 709 Expect(err).NotTo(HaveOccurred()) 710 711 replicas := int32(3) 712 deployStatus := appsv1.DeploymentStatus{ 713 ReadyReplicas: 1, 714 Replicas: replicas, 715 } 716 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, func() error { 717 Expect(deploymentScaler(deploy, replicas)()).To(Succeed()) 718 return deploymentStatusr(deploy, deployStatus)() 719 }) 720 By("returning no error") 721 Expect(err).NotTo(HaveOccurred()) 722 723 By("returning OperationResultUpdatedStatus") 724 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdatedStatus)) 725 726 assertLocalDeployWasUpdated(nil) 727 }) 728 729 It("patches resource and not empty status", func() { 730 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr) 731 732 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 733 Expect(err).NotTo(HaveOccurred()) 734 735 replicas := int32(3) 736 deployStatus := appsv1.DeploymentStatus{ 737 ReadyReplicas: 1, 738 Replicas: replicas, 739 } 740 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, func() error { 741 Expect(deploymentScaler(deploy, replicas)()).To(Succeed()) 742 return deploymentStatusr(deploy, deployStatus)() 743 }) 744 By("returning no error") 745 Expect(err).NotTo(HaveOccurred()) 746 747 By("returning OperationResultUpdatedStatus") 748 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdatedStatus)) 749 750 assertLocalDeployWasUpdated(nil) 751 752 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, func() error { 753 deploy.Spec.Replicas = ptr.To(int32(5)) 754 deploy.Status.Conditions = []appsv1.DeploymentCondition{{ 755 Type: appsv1.DeploymentProgressing, 756 Status: corev1.ConditionTrue, 757 }} 758 return nil 759 }) 760 By("returning no error") 761 Expect(err).NotTo(HaveOccurred()) 762 763 By("returning OperationResultUpdatedStatus") 764 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultUpdatedStatus)) 765 766 assertLocalDeployStatusWasUpdated(nil) 767 }) 768 769 It("errors when MutateFn changes object name on creation", func() { 770 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, func() error { 771 Expect(specr()).To(Succeed()) 772 return deploymentRenamer(deploy)() 773 }) 774 775 By("returning error") 776 Expect(err).To(HaveOccurred()) 777 778 By("returning OperationResultNone") 779 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 780 }) 781 782 It("errors when MutateFn renames an object", func() { 783 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr) 784 785 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 786 Expect(err).NotTo(HaveOccurred()) 787 788 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentRenamer(deploy)) 789 790 By("returning error") 791 Expect(err).To(HaveOccurred()) 792 793 By("returning OperationResultNone") 794 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 795 }) 796 797 It("errors when object namespace changes", func() { 798 op, err := controllerutil.CreateOrPatch(context.TODO(), c, deploy, specr) 799 800 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultCreated)) 801 Expect(err).NotTo(HaveOccurred()) 802 803 op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, deploymentNamespaceChanger(deploy)) 804 805 By("returning error") 806 Expect(err).To(HaveOccurred()) 807 808 By("returning OperationResultNone") 809 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 810 }) 811 812 It("aborts immediately if there was an error initially retrieving the object", func() { 813 op, err := controllerutil.CreateOrPatch(context.TODO(), errorReader{c}, deploy, func() error { 814 Fail("Mutation method should not run") 815 return nil 816 }) 817 818 Expect(op).To(BeEquivalentTo(controllerutil.OperationResultNone)) 819 Expect(err).To(HaveOccurred()) 820 }) 821 }) 822 823 Describe("Finalizers", func() { 824 var deploy *appsv1.Deployment 825 826 Describe("AddFinalizer", func() { 827 deploy = &appsv1.Deployment{ 828 ObjectMeta: metav1.ObjectMeta{ 829 Finalizers: []string{}, 830 }, 831 } 832 833 It("should add the finalizer when not present", func() { 834 controllerutil.AddFinalizer(deploy, testFinalizer) 835 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer})) 836 }) 837 838 It("should not add the finalizer when already present", func() { 839 controllerutil.AddFinalizer(deploy, testFinalizer) 840 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer})) 841 }) 842 }) 843 844 Describe("RemoveFinalizer", func() { 845 It("should remove finalizer if present", func() { 846 controllerutil.RemoveFinalizer(deploy, testFinalizer) 847 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{})) 848 }) 849 850 It("should remove all equal finalizers if present", func() { 851 deploy.SetFinalizers(append(deploy.Finalizers, testFinalizer, testFinalizer)) 852 controllerutil.RemoveFinalizer(deploy, testFinalizer) 853 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{})) 854 }) 855 }) 856 857 Describe("AddFinalizer, which returns an indication of whether it modified the object's list of finalizers,", func() { 858 deploy = &appsv1.Deployment{ 859 ObjectMeta: metav1.ObjectMeta{ 860 Finalizers: []string{}, 861 }, 862 } 863 864 When("the object's list of finalizers has no instances of the input finalizer", func() { 865 It("should return true", func() { 866 Expect(controllerutil.AddFinalizer(deploy, testFinalizer)).To(BeTrue()) 867 }) 868 It("should add the input finalizer to the object's list of finalizers", func() { 869 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer})) 870 }) 871 }) 872 873 When("the object's list of finalizers has an instance of the input finalizer", func() { 874 It("should return false", func() { 875 Expect(controllerutil.AddFinalizer(deploy, testFinalizer)).To(BeFalse()) 876 }) 877 It("should not modify the object's list of finalizers", func() { 878 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer})) 879 }) 880 }) 881 }) 882 883 Describe("RemoveFinalizer, which returns an indication of whether it modified the object's list of finalizers,", func() { 884 When("the object's list of finalizers has no instances of the input finalizer", func() { 885 It("should return false", func() { 886 Expect(controllerutil.RemoveFinalizer(deploy, testFinalizer1)).To(BeFalse()) 887 }) 888 It("should not modify the object's list of finalizers", func() { 889 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{testFinalizer})) 890 }) 891 }) 892 893 When("the object's list of finalizers has one instance of the input finalizer", func() { 894 It("should return true", func() { 895 Expect(controllerutil.RemoveFinalizer(deploy, testFinalizer)).To(BeTrue()) 896 }) 897 It("should remove the instance of the input finalizer from the object's list of finalizers", func() { 898 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{})) 899 }) 900 }) 901 902 When("the object's list of finalizers has multiple instances of the input finalizer", func() { 903 It("should return true", func() { 904 deploy.SetFinalizers(append(deploy.Finalizers, testFinalizer, testFinalizer)) 905 Expect(controllerutil.RemoveFinalizer(deploy, testFinalizer)).To(BeTrue()) 906 }) 907 It("should remove each instance of the input finalizer from the object's list of finalizers", func() { 908 Expect(deploy.ObjectMeta.GetFinalizers()).To(Equal([]string{})) 909 }) 910 }) 911 }) 912 913 Describe("ContainsFinalizer", func() { 914 It("should check that finalizer is present", func() { 915 controllerutil.AddFinalizer(deploy, testFinalizer) 916 Expect(controllerutil.ContainsFinalizer(deploy, testFinalizer)).To(BeTrue()) 917 }) 918 919 It("should check that finalizer is not present after RemoveFinalizer call", func() { 920 controllerutil.RemoveFinalizer(deploy, testFinalizer) 921 Expect(controllerutil.ContainsFinalizer(deploy, testFinalizer)).To(BeFalse()) 922 }) 923 }) 924 }) 925 }) 926 927 const ( 928 testFinalizer = "foo.bar.baz" 929 testFinalizer1 = testFinalizer + "1" 930 ) 931 932 var ( 933 _ runtime.Object = &errRuntimeObj{} 934 _ metav1.Object = &errMetaObj{} 935 ) 936 937 type errRuntimeObj struct { 938 runtime.TypeMeta 939 } 940 941 func (o *errRuntimeObj) DeepCopyObject() runtime.Object { 942 return &errRuntimeObj{} 943 } 944 945 type errMetaObj struct { 946 metav1.ObjectMeta 947 } 948 949 func deploymentSpecr(deploy *appsv1.Deployment, spec appsv1.DeploymentSpec) controllerutil.MutateFn { 950 return func() error { 951 deploy.Spec = spec 952 return nil 953 } 954 } 955 956 func deploymentStatusr(deploy *appsv1.Deployment, status appsv1.DeploymentStatus) controllerutil.MutateFn { 957 return func() error { 958 deploy.Status = status 959 return nil 960 } 961 } 962 963 var deploymentIdentity controllerutil.MutateFn = func() error { 964 return nil 965 } 966 967 func deploymentRenamer(deploy *appsv1.Deployment) controllerutil.MutateFn { 968 return func() error { 969 deploy.Name = fmt.Sprintf("%s-1", deploy.Name) 970 return nil 971 } 972 } 973 974 func deploymentNamespaceChanger(deploy *appsv1.Deployment) controllerutil.MutateFn { 975 return func() error { 976 deploy.Namespace = fmt.Sprintf("%s-1", deploy.Namespace) 977 return nil 978 } 979 } 980 981 func deploymentScaler(deploy *appsv1.Deployment, replicas int32) controllerutil.MutateFn { 982 fn := func() error { 983 deploy.Spec.Replicas = &replicas 984 return nil 985 } 986 return fn 987 } 988 989 type errorReader struct { 990 client.Client 991 } 992 993 func (e errorReader) Get(ctx context.Context, key client.ObjectKey, into client.Object, opts ...client.GetOption) error { 994 return fmt.Errorf("unexpected error") 995 }