sigs.k8s.io/controller-runtime@v0.18.2/pkg/builder/controller_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 builder 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "sync/atomic" 24 25 "github.com/go-logr/logr" 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 appsv1 "k8s.io/api/apps/v1" 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/types" 34 "k8s.io/client-go/rest" 35 "k8s.io/client-go/util/workqueue" 36 "k8s.io/utils/ptr" 37 38 "sigs.k8s.io/controller-runtime/pkg/cache" 39 "sigs.k8s.io/controller-runtime/pkg/client" 40 "sigs.k8s.io/controller-runtime/pkg/config" 41 "sigs.k8s.io/controller-runtime/pkg/controller" 42 "sigs.k8s.io/controller-runtime/pkg/event" 43 "sigs.k8s.io/controller-runtime/pkg/handler" 44 "sigs.k8s.io/controller-runtime/pkg/manager" 45 "sigs.k8s.io/controller-runtime/pkg/predicate" 46 "sigs.k8s.io/controller-runtime/pkg/reconcile" 47 "sigs.k8s.io/controller-runtime/pkg/scheme" 48 ) 49 50 type typedNoop struct{} 51 52 func (typedNoop) Reconcile(context.Context, reconcile.Request) (reconcile.Result, error) { 53 return reconcile.Result{}, nil 54 } 55 56 type testLogger struct { 57 logr.Logger 58 } 59 60 func (l *testLogger) Init(logr.RuntimeInfo) { 61 } 62 63 func (l *testLogger) Enabled(int) bool { 64 return true 65 } 66 67 func (l *testLogger) Info(level int, msg string, keysAndValues ...interface{}) { 68 } 69 70 func (l *testLogger) WithValues(keysAndValues ...interface{}) logr.LogSink { 71 return l 72 } 73 74 func (l *testLogger) WithName(name string) logr.LogSink { 75 return l 76 } 77 78 var _ = Describe("application", func() { 79 BeforeEach(func() { 80 newController = controller.New 81 }) 82 83 noop := reconcile.Func(func(context.Context, reconcile.Request) (reconcile.Result, error) { 84 return reconcile.Result{}, nil 85 }) 86 87 Describe("New", func() { 88 It("should return success if given valid objects", func() { 89 By("creating a controller manager") 90 m, err := manager.New(cfg, manager.Options{}) 91 Expect(err).NotTo(HaveOccurred()) 92 93 instance, err := ControllerManagedBy(m). 94 For(&appsv1.ReplicaSet{}). 95 Owns(&appsv1.ReplicaSet{}). 96 Build(noop) 97 Expect(err).NotTo(HaveOccurred()) 98 Expect(instance).NotTo(BeNil()) 99 }) 100 101 It("should return error if given two apiType objects in For function", func() { 102 By("creating a controller manager") 103 m, err := manager.New(cfg, manager.Options{}) 104 Expect(err).NotTo(HaveOccurred()) 105 106 instance, err := ControllerManagedBy(m). 107 For(&appsv1.ReplicaSet{}). 108 For(&appsv1.Deployment{}). 109 Owns(&appsv1.ReplicaSet{}). 110 Build(noop) 111 Expect(err).To(MatchError(ContainSubstring("For(...) should only be called once, could not assign multiple objects for reconciliation"))) 112 Expect(instance).To(BeNil()) 113 }) 114 115 It("should return an error if For and Named function are not called", func() { 116 By("creating a controller manager") 117 m, err := manager.New(cfg, manager.Options{}) 118 Expect(err).NotTo(HaveOccurred()) 119 120 instance, err := ControllerManagedBy(m). 121 Watches(&appsv1.ReplicaSet{}, &handler.EnqueueRequestForObject{}). 122 Build(noop) 123 Expect(err).To(MatchError(ContainSubstring("one of For() or Named() must be called"))) 124 Expect(instance).To(BeNil()) 125 }) 126 127 It("should return an error when using Owns without For", func() { 128 By("creating a controller manager") 129 m, err := manager.New(cfg, manager.Options{}) 130 Expect(err).NotTo(HaveOccurred()) 131 132 instance, err := ControllerManagedBy(m). 133 Named("my_controller"). 134 Owns(&appsv1.ReplicaSet{}). 135 Build(noop) 136 Expect(err).To(MatchError(ContainSubstring("Owns() can only be used together with For()"))) 137 Expect(instance).To(BeNil()) 138 139 }) 140 141 It("should return an error when there are no watches", func() { 142 By("creating a controller manager") 143 m, err := manager.New(cfg, manager.Options{}) 144 Expect(err).NotTo(HaveOccurred()) 145 146 instance, err := ControllerManagedBy(m). 147 Named("my_controller"). 148 Build(noop) 149 Expect(err).To(MatchError(ContainSubstring("there are no watches configured, controller will never get triggered. Use For(), Owns(), Watches() or WatchesRawSource() to set them up"))) 150 Expect(instance).To(BeNil()) 151 }) 152 153 It("should allow creating a controllerw without calling For", func() { 154 By("creating a controller manager") 155 m, err := manager.New(cfg, manager.Options{}) 156 Expect(err).NotTo(HaveOccurred()) 157 158 instance, err := ControllerManagedBy(m). 159 Named("my_controller"). 160 Watches(&appsv1.ReplicaSet{}, &handler.EnqueueRequestForObject{}). 161 Build(noop) 162 Expect(err).NotTo(HaveOccurred()) 163 Expect(instance).NotTo(BeNil()) 164 }) 165 166 It("should return an error if there is no GVK for an object, and thus we can't default the controller name", func() { 167 By("creating a controller manager") 168 m, err := manager.New(cfg, manager.Options{}) 169 Expect(err).NotTo(HaveOccurred()) 170 171 By("creating a controller with a bad For type") 172 instance, err := ControllerManagedBy(m). 173 For(&fakeType{}). 174 Owns(&appsv1.ReplicaSet{}). 175 Build(noop) 176 Expect(err).To(MatchError(ContainSubstring("no kind is registered for the type builder.fakeType"))) 177 Expect(instance).To(BeNil()) 178 179 // NB(directxman12): we don't test non-for types, since errors for 180 // them now manifest on controller.Start, not controller.Watch. Errors on the For type 181 // manifest when we try to default the controller name, which is good to double check. 182 }) 183 184 It("should return an error if it cannot create the controller", func() { 185 newController = func(name string, mgr manager.Manager, options controller.Options) ( 186 controller.Controller, error) { 187 return nil, fmt.Errorf("expected error") 188 } 189 190 By("creating a controller manager") 191 m, err := manager.New(cfg, manager.Options{}) 192 Expect(err).NotTo(HaveOccurred()) 193 194 instance, err := ControllerManagedBy(m). 195 For(&appsv1.ReplicaSet{}). 196 Owns(&appsv1.ReplicaSet{}). 197 Build(noop) 198 Expect(err).To(HaveOccurred()) 199 Expect(err.Error()).To(ContainSubstring("expected error")) 200 Expect(instance).To(BeNil()) 201 }) 202 203 It("should override max concurrent reconcilers during creation of controller", func() { 204 const maxConcurrentReconciles = 5 205 newController = func(name string, mgr manager.Manager, options controller.Options) ( 206 controller.Controller, error) { 207 if options.MaxConcurrentReconciles == maxConcurrentReconciles { 208 return controller.New(name, mgr, options) 209 } 210 return nil, fmt.Errorf("max concurrent reconcilers expected %d but found %d", maxConcurrentReconciles, options.MaxConcurrentReconciles) 211 } 212 213 By("creating a controller manager") 214 m, err := manager.New(cfg, manager.Options{}) 215 Expect(err).NotTo(HaveOccurred()) 216 217 instance, err := ControllerManagedBy(m). 218 For(&appsv1.ReplicaSet{}). 219 Owns(&appsv1.ReplicaSet{}). 220 WithOptions(controller.Options{MaxConcurrentReconciles: maxConcurrentReconciles}). 221 Build(noop) 222 Expect(err).NotTo(HaveOccurred()) 223 Expect(instance).NotTo(BeNil()) 224 }) 225 226 It("should override max concurrent reconcilers during creation of controller, when using", func() { 227 const maxConcurrentReconciles = 10 228 newController = func(name string, mgr manager.Manager, options controller.Options) ( 229 controller.Controller, error) { 230 if options.MaxConcurrentReconciles == maxConcurrentReconciles { 231 return controller.New(name, mgr, options) 232 } 233 return nil, fmt.Errorf("max concurrent reconcilers expected %d but found %d", maxConcurrentReconciles, options.MaxConcurrentReconciles) 234 } 235 236 By("creating a controller manager") 237 m, err := manager.New(cfg, manager.Options{ 238 Controller: config.Controller{ 239 GroupKindConcurrency: map[string]int{ 240 "ReplicaSet.apps": maxConcurrentReconciles, 241 }, 242 }, 243 }) 244 Expect(err).NotTo(HaveOccurred()) 245 246 instance, err := ControllerManagedBy(m). 247 For(&appsv1.ReplicaSet{}). 248 Owns(&appsv1.ReplicaSet{}). 249 Build(noop) 250 Expect(err).NotTo(HaveOccurred()) 251 Expect(instance).NotTo(BeNil()) 252 }) 253 254 It("should override rate limiter during creation of controller", func() { 255 rateLimiter := workqueue.DefaultItemBasedRateLimiter() 256 newController = func(name string, mgr manager.Manager, options controller.Options) (controller.Controller, error) { 257 if options.RateLimiter == rateLimiter { 258 return controller.New(name, mgr, options) 259 } 260 return nil, fmt.Errorf("rate limiter expected %T but found %T", rateLimiter, options.RateLimiter) 261 } 262 263 By("creating a controller manager") 264 m, err := manager.New(cfg, manager.Options{}) 265 Expect(err).NotTo(HaveOccurred()) 266 267 instance, err := ControllerManagedBy(m). 268 For(&appsv1.ReplicaSet{}). 269 Owns(&appsv1.ReplicaSet{}). 270 WithOptions(controller.Options{RateLimiter: rateLimiter}). 271 Build(noop) 272 Expect(err).NotTo(HaveOccurred()) 273 Expect(instance).NotTo(BeNil()) 274 }) 275 276 It("should override logger during creation of controller", func() { 277 278 logger := &testLogger{} 279 newController = func(name string, mgr manager.Manager, options controller.Options) (controller.Controller, error) { 280 if options.LogConstructor(nil).GetSink() == logger { 281 return controller.New(name, mgr, options) 282 } 283 return nil, fmt.Errorf("logger expected %T but found %T", logger, options.LogConstructor) 284 } 285 286 By("creating a controller manager") 287 m, err := manager.New(cfg, manager.Options{}) 288 Expect(err).NotTo(HaveOccurred()) 289 290 instance, err := ControllerManagedBy(m). 291 For(&appsv1.ReplicaSet{}). 292 Owns(&appsv1.ReplicaSet{}). 293 WithLogConstructor(func(request *reconcile.Request) logr.Logger { 294 return logr.New(logger) 295 }). 296 Build(noop) 297 Expect(err).NotTo(HaveOccurred()) 298 Expect(instance).NotTo(BeNil()) 299 }) 300 301 It("should not allow multiple reconcilers during creation of controller", func() { 302 newController = func(name string, mgr manager.Manager, options controller.Options) (controller.Controller, error) { 303 if options.Reconciler != (typedNoop{}) { 304 return nil, fmt.Errorf("Custom reconciler expected %T but found %T", typedNoop{}, options.Reconciler) 305 } 306 return controller.New(name, mgr, options) 307 } 308 309 By("creating a controller manager") 310 m, err := manager.New(cfg, manager.Options{}) 311 Expect(err).NotTo(HaveOccurred()) 312 313 instance, err := ControllerManagedBy(m). 314 For(&appsv1.ReplicaSet{}). 315 Owns(&appsv1.ReplicaSet{}). 316 WithOptions(controller.Options{Reconciler: typedNoop{}}). 317 Build(noop) 318 Expect(err).To(HaveOccurred()) 319 Expect(instance).To(BeNil()) 320 }) 321 322 It("should allow multiple controllers for the same kind", func() { 323 By("creating a controller manager") 324 m, err := manager.New(cfg, manager.Options{}) 325 Expect(err).NotTo(HaveOccurred()) 326 327 By("registering the type in the Scheme") 328 builder := scheme.Builder{GroupVersion: testDefaultValidatorGVK.GroupVersion()} 329 builder.Register(&TestDefaultValidator{}, &TestDefaultValidatorList{}) 330 err = builder.AddToScheme(m.GetScheme()) 331 Expect(err).NotTo(HaveOccurred()) 332 333 By("creating the 1st controller") 334 ctrl1, err := ControllerManagedBy(m). 335 For(&TestDefaultValidator{}). 336 Owns(&appsv1.ReplicaSet{}). 337 Build(noop) 338 Expect(err).NotTo(HaveOccurred()) 339 Expect(ctrl1).NotTo(BeNil()) 340 341 By("creating the 2nd controller") 342 ctrl2, err := ControllerManagedBy(m). 343 For(&TestDefaultValidator{}). 344 Owns(&appsv1.ReplicaSet{}). 345 Build(noop) 346 Expect(err).NotTo(HaveOccurred()) 347 Expect(ctrl2).NotTo(BeNil()) 348 }) 349 }) 350 351 Describe("Start with ControllerManagedBy", func() { 352 It("should Reconcile Owns objects", func() { 353 m, err := manager.New(cfg, manager.Options{}) 354 Expect(err).NotTo(HaveOccurred()) 355 356 bldr := ControllerManagedBy(m). 357 For(&appsv1.Deployment{}). 358 Owns(&appsv1.ReplicaSet{}) 359 360 ctx, cancel := context.WithCancel(context.Background()) 361 defer cancel() 362 doReconcileTest(ctx, "3", m, false, bldr) 363 }) 364 365 It("should Reconcile Owns objects for every owner", func() { 366 m, err := manager.New(cfg, manager.Options{}) 367 Expect(err).NotTo(HaveOccurred()) 368 369 bldr := ControllerManagedBy(m). 370 For(&appsv1.Deployment{}). 371 Owns(&appsv1.ReplicaSet{}, MatchEveryOwner) 372 373 ctx, cancel := context.WithCancel(context.Background()) 374 defer cancel() 375 doReconcileTest(ctx, "12", m, false, bldr) 376 }) 377 378 It("should Reconcile Watches objects", func() { 379 m, err := manager.New(cfg, manager.Options{}) 380 Expect(err).NotTo(HaveOccurred()) 381 382 bldr := ControllerManagedBy(m). 383 For(&appsv1.Deployment{}). 384 Watches( // Equivalent of Owns 385 &appsv1.ReplicaSet{}, 386 handler.EnqueueRequestForOwner(m.GetScheme(), m.GetRESTMapper(), &appsv1.Deployment{}, handler.OnlyControllerOwner()), 387 ) 388 389 ctx, cancel := context.WithCancel(context.Background()) 390 defer cancel() 391 doReconcileTest(ctx, "4", m, true, bldr) 392 }) 393 394 It("should Reconcile without For", func() { 395 m, err := manager.New(cfg, manager.Options{}) 396 Expect(err).NotTo(HaveOccurred()) 397 398 bldr := ControllerManagedBy(m). 399 Named("Deployment"). 400 Watches( // Equivalent of For 401 &appsv1.Deployment{}, &handler.EnqueueRequestForObject{}). 402 Watches( // Equivalent of Owns 403 &appsv1.ReplicaSet{}, 404 handler.EnqueueRequestForOwner(m.GetScheme(), m.GetRESTMapper(), &appsv1.Deployment{}, handler.OnlyControllerOwner()), 405 ) 406 407 ctx, cancel := context.WithCancel(context.Background()) 408 defer cancel() 409 doReconcileTest(ctx, "9", m, true, bldr) 410 }) 411 }) 412 413 Describe("Set custom predicates", func() { 414 It("should execute registered predicates only for assigned kind", func() { 415 m, err := manager.New(cfg, manager.Options{}) 416 Expect(err).NotTo(HaveOccurred()) 417 418 var ( 419 deployPrctExecuted = false 420 replicaSetPrctExecuted = false 421 allPrctExecuted = int64(0) 422 ) 423 424 deployPrct := predicate.Funcs{ 425 CreateFunc: func(e event.CreateEvent) bool { 426 defer GinkgoRecover() 427 // check that it was called only for deployment 428 Expect(e.Object).To(BeAssignableToTypeOf(&appsv1.Deployment{})) 429 deployPrctExecuted = true 430 return true 431 }, 432 } 433 434 replicaSetPrct := predicate.Funcs{ 435 CreateFunc: func(e event.CreateEvent) bool { 436 defer GinkgoRecover() 437 // check that it was called only for replicaset 438 Expect(e.Object).To(BeAssignableToTypeOf(&appsv1.ReplicaSet{})) 439 replicaSetPrctExecuted = true 440 return true 441 }, 442 } 443 444 allPrct := predicate.Funcs{ 445 CreateFunc: func(e event.CreateEvent) bool { 446 defer GinkgoRecover() 447 // check that it was called for all registered kinds 448 Expect(e.Object).Should(Or( 449 BeAssignableToTypeOf(&appsv1.Deployment{}), 450 BeAssignableToTypeOf(&appsv1.ReplicaSet{}), 451 )) 452 453 atomic.AddInt64(&allPrctExecuted, 1) 454 return true 455 }, 456 } 457 458 bldr := ControllerManagedBy(m). 459 For(&appsv1.Deployment{}, WithPredicates(deployPrct)). 460 Owns(&appsv1.ReplicaSet{}, WithPredicates(replicaSetPrct)). 461 WithEventFilter(allPrct) 462 463 ctx, cancel := context.WithCancel(context.Background()) 464 defer cancel() 465 doReconcileTest(ctx, "5", m, true, bldr) 466 467 Expect(deployPrctExecuted).To(BeTrue(), "Deploy predicated should be called at least once") 468 Expect(replicaSetPrctExecuted).To(BeTrue(), "ReplicaSet predicated should be called at least once") 469 Expect(allPrctExecuted).To(BeNumerically(">=", 2), "Global Predicated should be called at least twice") 470 }) 471 }) 472 473 Describe("watching with projections", func() { 474 var mgr manager.Manager 475 BeforeEach(func() { 476 // use a cache that intercepts requests for fully typed objects to 477 // ensure we use the projected versions 478 var err error 479 mgr, err = manager.New(cfg, manager.Options{NewCache: newNonTypedOnlyCache}) 480 Expect(err).NotTo(HaveOccurred()) 481 }) 482 483 It("should support multiple controllers watching the same metadata kind", func() { 484 bldr1 := ControllerManagedBy(mgr).For(&appsv1.Deployment{}, OnlyMetadata) 485 bldr2 := ControllerManagedBy(mgr).For(&appsv1.Deployment{}, OnlyMetadata) 486 487 ctx, cancel := context.WithCancel(context.Background()) 488 defer cancel() 489 490 doReconcileTest(ctx, "6", mgr, true, bldr1, bldr2) 491 }) 492 493 It("should support watching For, Owns, and Watch as metadata", func() { 494 statefulSetMaps := make(chan *metav1.PartialObjectMetadata) 495 496 bldr := ControllerManagedBy(mgr). 497 For(&appsv1.Deployment{}, OnlyMetadata). 498 Owns(&appsv1.ReplicaSet{}, OnlyMetadata). 499 Watches(&appsv1.StatefulSet{}, 500 handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request { 501 defer GinkgoRecover() 502 503 ometa := o.(*metav1.PartialObjectMetadata) 504 statefulSetMaps <- ometa 505 506 // Validate that the GVK is not empty when dealing with PartialObjectMetadata objects. 507 Expect(o.GetObjectKind().GroupVersionKind()).To(Equal(schema.GroupVersionKind{ 508 Group: "apps", 509 Version: "v1", 510 Kind: "StatefulSet", 511 })) 512 return nil 513 }), 514 OnlyMetadata) 515 516 ctx, cancel := context.WithCancel(context.Background()) 517 defer cancel() 518 doReconcileTest(ctx, "8", mgr, true, bldr) 519 520 By("Creating a new stateful set") 521 set := &appsv1.StatefulSet{ 522 ObjectMeta: metav1.ObjectMeta{ 523 Namespace: "default", 524 Name: "test1", 525 Labels: map[string]string{ 526 "foo": "bar", 527 }, 528 }, 529 Spec: appsv1.StatefulSetSpec{ 530 Selector: &metav1.LabelSelector{ 531 MatchLabels: map[string]string{"foo": "bar"}, 532 }, 533 Template: corev1.PodTemplateSpec{ 534 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, 535 Spec: corev1.PodSpec{ 536 Containers: []corev1.Container{ 537 { 538 Name: "nginx", 539 Image: "nginx", 540 }, 541 }, 542 }, 543 }, 544 }, 545 } 546 err := mgr.GetClient().Create(context.TODO(), set) 547 Expect(err).NotTo(HaveOccurred()) 548 549 By("Checking that the mapping function has been called") 550 Eventually(func() bool { 551 metaSet := <-statefulSetMaps 552 Expect(metaSet.Name).To(Equal(set.Name)) 553 Expect(metaSet.Namespace).To(Equal(set.Namespace)) 554 Expect(metaSet.Labels).To(Equal(set.Labels)) 555 return true 556 }).Should(BeTrue()) 557 }) 558 }) 559 }) 560 561 // newNonTypedOnlyCache returns a new cache that wraps the normal cache, 562 // returning an error if normal, typed objects have informers requested. 563 func newNonTypedOnlyCache(config *rest.Config, opts cache.Options) (cache.Cache, error) { 564 normalCache, err := cache.New(config, opts) 565 if err != nil { 566 return nil, err 567 } 568 return &nonTypedOnlyCache{ 569 Cache: normalCache, 570 }, nil 571 } 572 573 // nonTypedOnlyCache is a cache.Cache that only provides metadata & 574 // unstructured informers. 575 type nonTypedOnlyCache struct { 576 cache.Cache 577 } 578 579 func (c *nonTypedOnlyCache) GetInformer(ctx context.Context, obj client.Object, opts ...cache.InformerGetOption) (cache.Informer, error) { 580 switch obj.(type) { 581 case (*metav1.PartialObjectMetadata): 582 return c.Cache.GetInformer(ctx, obj, opts...) 583 default: 584 return nil, fmt.Errorf("did not want to provide an informer for normal type %T", obj) 585 } 586 } 587 func (c *nonTypedOnlyCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...cache.InformerGetOption) (cache.Informer, error) { 588 return nil, fmt.Errorf("don't try to sidestep the restriction on informer types by calling GetInformerForKind") 589 } 590 591 // TODO(directxman12): this function has too many arguments, and the whole 592 // "nameSuffix" think is a bit of a hack It should be cleaned up significantly by someone with a bit of time. 593 func doReconcileTest(ctx context.Context, nameSuffix string, mgr manager.Manager, complete bool, blders ...*Builder) { 594 deployName := "deploy-name-" + nameSuffix 595 rsName := "rs-name-" + nameSuffix 596 597 By("Creating the application") 598 ch := make(chan reconcile.Request) 599 fn := reconcile.Func(func(_ context.Context, req reconcile.Request) (reconcile.Result, error) { 600 defer GinkgoRecover() 601 if !strings.HasSuffix(req.Name, nameSuffix) { 602 // From different test, ignore this request. Etcd is shared across tests. 603 return reconcile.Result{}, nil 604 } 605 ch <- req 606 return reconcile.Result{}, nil 607 }) 608 609 for _, blder := range blders { 610 if complete { 611 err := blder.Complete(fn) 612 Expect(err).NotTo(HaveOccurred()) 613 } else { 614 var err error 615 var c controller.Controller 616 c, err = blder.Build(fn) 617 Expect(err).NotTo(HaveOccurred()) 618 Expect(c).NotTo(BeNil()) 619 } 620 } 621 622 By("Starting the application") 623 go func() { 624 defer GinkgoRecover() 625 Expect(mgr.Start(ctx)).NotTo(HaveOccurred()) 626 }() 627 628 By("Creating a Deployment") 629 // Expect a Reconcile when the Deployment is managedObjects. 630 dep := &appsv1.Deployment{ 631 ObjectMeta: metav1.ObjectMeta{ 632 Namespace: "default", 633 Name: deployName, 634 }, 635 Spec: appsv1.DeploymentSpec{ 636 Selector: &metav1.LabelSelector{ 637 MatchLabels: map[string]string{"foo": "bar"}, 638 }, 639 Template: corev1.PodTemplateSpec{ 640 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, 641 Spec: corev1.PodSpec{ 642 Containers: []corev1.Container{ 643 { 644 Name: "nginx", 645 Image: "nginx", 646 }, 647 }, 648 }, 649 }, 650 }, 651 } 652 err := mgr.GetClient().Create(ctx, dep) 653 Expect(err).NotTo(HaveOccurred()) 654 655 By("Waiting for the Deployment Reconcile") 656 Eventually(ch).Should(Receive(Equal(reconcile.Request{ 657 NamespacedName: types.NamespacedName{Namespace: "default", Name: deployName}}))) 658 659 By("Creating a ReplicaSet") 660 // Expect a Reconcile when an Owned object is managedObjects. 661 rs := &appsv1.ReplicaSet{ 662 ObjectMeta: metav1.ObjectMeta{ 663 Namespace: "default", 664 Name: rsName, 665 Labels: dep.Spec.Selector.MatchLabels, 666 OwnerReferences: []metav1.OwnerReference{ 667 { 668 Name: deployName, 669 Kind: "Deployment", 670 APIVersion: "apps/v1", 671 Controller: ptr.To(true), 672 UID: dep.UID, 673 }, 674 }, 675 }, 676 Spec: appsv1.ReplicaSetSpec{ 677 Selector: dep.Spec.Selector, 678 Template: dep.Spec.Template, 679 }, 680 } 681 err = mgr.GetClient().Create(ctx, rs) 682 Expect(err).NotTo(HaveOccurred()) 683 684 By("Waiting for the ReplicaSet Reconcile") 685 Eventually(ch).Should(Receive(Equal(reconcile.Request{ 686 NamespacedName: types.NamespacedName{Namespace: "default", Name: deployName}}))) 687 } 688 689 var _ runtime.Object = &fakeType{} 690 691 type fakeType struct { 692 metav1.TypeMeta 693 metav1.ObjectMeta 694 } 695 696 func (*fakeType) GetObjectKind() schema.ObjectKind { return nil } 697 func (*fakeType) DeepCopyObject() runtime.Object { return nil }