@@ -29,6 +29,7 @@ import (
29
29
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30
30
"k8s.io/apimachinery/pkg/runtime"
31
31
"k8s.io/apimachinery/pkg/types"
32
+ "k8s.io/apimachinery/pkg/util/intstr"
32
33
"k8s.io/client-go/kubernetes/scheme"
33
34
"k8s.io/utils/ptr"
34
35
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -454,6 +455,9 @@ var _ = Describe("Controllerutil", func() {
454
455
var deplKey types.NamespacedName
455
456
var specr controllerutil.MutateFn
456
457
458
+ var deplSpecWithDefaults appsv1.DeploymentSpec
459
+ var specrWithDefaultsr controllerutil.MutateFn
460
+
457
461
BeforeEach (func () {
458
462
deploy = & appsv1.Deployment {
459
463
ObjectMeta : metav1.ObjectMeta {
@@ -483,12 +487,59 @@ var _ = Describe("Controllerutil", func() {
483
487
},
484
488
}
485
489
490
+ // deployment with all defaults set
491
+ deplSpecWithDefaults = appsv1.DeploymentSpec {
492
+ Selector : & metav1.LabelSelector {
493
+ MatchLabels : map [string ]string {"foo" : "bar" },
494
+ },
495
+ Replicas : ptr .To (int32 (1 )),
496
+ Template : corev1.PodTemplateSpec {
497
+ ObjectMeta : metav1.ObjectMeta {
498
+ Labels : map [string ]string {
499
+ "foo" : "bar" ,
500
+ },
501
+ },
502
+ Spec : corev1.PodSpec {
503
+ RestartPolicy : corev1 .RestartPolicyAlways ,
504
+ TerminationGracePeriodSeconds : ptr .To (int64 (30 )),
505
+ DNSPolicy : corev1 .DNSClusterFirst ,
506
+ SecurityContext : & corev1.PodSecurityContext {},
507
+ SchedulerName : "default-scheduler" ,
508
+ Containers : []corev1.Container {
509
+ {
510
+ Name : "busybox" ,
511
+ Image : "busybox" ,
512
+ TerminationMessagePath : "/dev/termination-log" ,
513
+ TerminationMessagePolicy : corev1 .TerminationMessageReadFile ,
514
+ ImagePullPolicy : corev1 .PullAlways ,
515
+ },
516
+ },
517
+ },
518
+ },
519
+ Strategy : appsv1.DeploymentStrategy {
520
+ Type : appsv1 .RollingUpdateDeploymentStrategyType ,
521
+ RollingUpdate : & appsv1.RollingUpdateDeployment {
522
+ MaxUnavailable : & intstr.IntOrString {
523
+ Type : intstr .String ,
524
+ StrVal : "25%" ,
525
+ },
526
+ MaxSurge : & intstr.IntOrString {
527
+ Type : intstr .String ,
528
+ StrVal : "25%" ,
529
+ },
530
+ },
531
+ },
532
+ RevisionHistoryLimit : ptr .To (int32 (10 )),
533
+ ProgressDeadlineSeconds : ptr .To (int32 (600 )),
534
+ }
535
+
486
536
deplKey = types.NamespacedName {
487
537
Name : deploy .Name ,
488
538
Namespace : deploy .Namespace ,
489
539
}
490
540
491
541
specr = deploymentSpecr (deploy , deplSpec )
542
+ specrWithDefaultsr = deploymentSpecr (deploy , deplSpecWithDefaults )
492
543
})
493
544
494
545
It ("creates a new object if one doesn't exists" , func () {
@@ -543,6 +594,59 @@ var _ = Describe("Controllerutil", func() {
543
594
Expect (op ).To (BeEquivalentTo (controllerutil .OperationResultNone ))
544
595
})
545
596
597
+ // The next two tests verify that CreateOrUpdate will always report an update unless the
598
+ // object has *all* of its defaults set in the mutate function.
599
+
600
+ It ("is idempotent if all defaults are set" , func () {
601
+ op , err := controllerutil .CreateOrUpdate (context .TODO (), c , deploy , specrWithDefaultsr )
602
+
603
+ Expect (op ).To (BeEquivalentTo (controllerutil .OperationResultCreated ))
604
+ Expect (err ).NotTo (HaveOccurred ())
605
+
606
+ Expect (deploy .Spec ).To (BeEquivalentTo (deplSpecWithDefaults ))
607
+
608
+ op , err = controllerutil .CreateOrUpdate (context .TODO (), c , deploy , specrWithDefaultsr )
609
+ By ("returning no error" )
610
+ Expect (err ).NotTo (HaveOccurred ())
611
+
612
+ By ("returning OperationResultNone" )
613
+ Expect (op ).To (BeEquivalentTo (controllerutil .OperationResultNone ))
614
+ })
615
+
616
+ It ("is idempotent even if defaults aren't set" , func () {
617
+ op , err := controllerutil .CreateOrUpdate (context .TODO (), c , deploy , specr )
618
+
619
+ Expect (op ).To (BeEquivalentTo (controllerutil .OperationResultCreated ))
620
+ Expect (err ).NotTo (HaveOccurred ())
621
+
622
+ op , err = controllerutil .CreateOrUpdate (context .TODO (), c , deploy , specr )
623
+ By ("returning no error" )
624
+ Expect (err ).NotTo (HaveOccurred ())
625
+
626
+ By ("returning OperationResultNone" )
627
+ Expect (op ).To (BeEquivalentTo (controllerutil .OperationResultNone ))
628
+ })
629
+
630
+ // The following test verifies that CreateOrUpdate will overwrite any changes made outside of
631
+ // the mutate function. This is working correctly, but needs to be documented.
632
+
633
+ It ("modifies objects that had non-identifier values erroneously pre-set" , func () {
634
+ op , err := controllerutil .CreateOrUpdate (context .TODO (), c , deploy , specr )
635
+
636
+ Expect (op ).To (BeEquivalentTo (controllerutil .OperationResultCreated ))
637
+ Expect (err ).NotTo (HaveOccurred ())
638
+
639
+ // Change the object outside of the mutate function. Despite deploymentIdentity being
640
+ // a literal no-op, this test fails because Replicas will be overwritten.
641
+ deploy .Spec .Replicas = ptr .To (int32 (5 ))
642
+ _ , err = controllerutil .CreateOrUpdate (context .TODO (), c , deploy , deploymentIdentity )
643
+ // This also fails, but that should be self-evident, as specr replaces the entire Spec.
644
+ // _, err = controllerutil.CreateOrUpdate(context.TODO(), c, deploy, specr)
645
+
646
+ Expect (err ).NotTo (HaveOccurred ())
647
+ Expect (deploy .Spec .Replicas ).To (HaveValue (BeEquivalentTo (int32 (5 ))))
648
+ })
649
+
546
650
It ("errors when MutateFn changes object name on creation" , func () {
547
651
op , err := controllerutil .CreateOrUpdate (context .TODO (), c , deploy , func () error {
548
652
Expect (specr ()).To (Succeed ())
0 commit comments