...

Source file src/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil_test.go

Documentation: sigs.k8s.io/controller-runtime/pkg/controller/controllerutil

     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  }
   996  

View as plain text