1
16
17 package client_test
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "sync/atomic"
24
25 . "github.com/onsi/ginkgo/v2"
26 . "github.com/onsi/gomega"
27
28 rbacv1 "k8s.io/api/rbac/v1"
29
30 appsv1 "k8s.io/api/apps/v1"
31 corev1 "k8s.io/api/core/v1"
32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33 "k8s.io/apimachinery/pkg/runtime"
34 "k8s.io/apimachinery/pkg/runtime/schema"
35 "k8s.io/apimachinery/pkg/types"
36 "sigs.k8s.io/controller-runtime/pkg/client"
37 )
38
39 var _ = Describe("NamespacedClient", func() {
40 var dep *appsv1.Deployment
41 var ns = "default"
42 ctx := context.Background()
43 var count uint64 = 0
44 var replicaCount int32 = 2
45
46 getClient := func() client.Client {
47 var sch = runtime.NewScheme()
48
49 err := rbacv1.AddToScheme(sch)
50 Expect(err).ToNot(HaveOccurred())
51 err = appsv1.AddToScheme(sch)
52 Expect(err).ToNot(HaveOccurred())
53
54 nonNamespacedClient, err := client.New(cfg, client.Options{Scheme: sch})
55 Expect(err).NotTo(HaveOccurred())
56 Expect(nonNamespacedClient).NotTo(BeNil())
57 return client.NewNamespacedClient(nonNamespacedClient, ns)
58 }
59
60 BeforeEach(func() {
61 atomic.AddUint64(&count, 1)
62 dep = &appsv1.Deployment{
63 ObjectMeta: metav1.ObjectMeta{
64 Name: fmt.Sprintf("namespaced-deployment-%v", count),
65 Labels: map[string]string{"name": fmt.Sprintf("namespaced-deployment-%v", count)},
66 },
67 Spec: appsv1.DeploymentSpec{
68 Replicas: &replicaCount,
69 Selector: &metav1.LabelSelector{
70 MatchLabels: map[string]string{"foo": "bar"},
71 },
72 Template: corev1.PodTemplateSpec{
73 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
74 Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
75 },
76 },
77 }
78 })
79
80 Describe("Get", func() {
81
82 BeforeEach(func() {
83 var err error
84 dep, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})
85 Expect(err).NotTo(HaveOccurred())
86 })
87
88 AfterEach(func() {
89 deleteDeployment(ctx, dep, ns)
90 })
91 It("should successfully Get a namespace-scoped object", func() {
92 name := types.NamespacedName{Name: dep.Name}
93 result := &appsv1.Deployment{}
94
95 Expect(getClient().Get(ctx, name, result)).NotTo(HaveOccurred())
96 Expect(result).To(BeEquivalentTo(dep))
97 })
98
99 It("should error when namespace provided in the object is different than the one "+
100 "specified in client", func() {
101 name := types.NamespacedName{Name: dep.Name, Namespace: "non-default"}
102 result := &appsv1.Deployment{}
103
104 Expect(getClient().Get(ctx, name, result)).To(HaveOccurred())
105 })
106 })
107
108 Describe("List", func() {
109 BeforeEach(func() {
110 var err error
111 dep, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})
112 Expect(err).NotTo(HaveOccurred())
113 })
114
115 AfterEach(func() {
116 deleteDeployment(ctx, dep, ns)
117 })
118
119 It("should successfully List objects when namespace is not specified with the object", func() {
120 result := &appsv1.DeploymentList{}
121 opts := client.MatchingLabels(dep.Labels)
122
123 Expect(getClient().List(ctx, result, opts)).NotTo(HaveOccurred())
124 Expect(len(result.Items)).To(BeEquivalentTo(1))
125 Expect(result.Items[0]).To(BeEquivalentTo(*dep))
126 })
127
128 It("should List objects from the namespace specified in the client", func() {
129 result := &appsv1.DeploymentList{}
130 opts := client.InNamespace("non-default")
131
132 Expect(getClient().List(ctx, result, opts)).NotTo(HaveOccurred())
133 Expect(len(result.Items)).To(BeEquivalentTo(1))
134 Expect(result.Items[0]).To(BeEquivalentTo(*dep))
135 })
136 })
137
138 Describe("Create", func() {
139 AfterEach(func() {
140 deleteDeployment(ctx, dep, ns)
141 })
142
143 It("should successfully create object in the right namespace", func() {
144 By("creating the object initially")
145 err := getClient().Create(ctx, dep)
146 Expect(err).NotTo(HaveOccurred())
147
148 By("checking if the object was created in the right namespace")
149 res, err := clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{})
150 Expect(err).NotTo(HaveOccurred())
151 Expect(res.GetNamespace()).To(BeEquivalentTo(ns))
152 })
153
154 It("should not create object if the namespace of the object is different", func() {
155 By("creating the object initially")
156 dep.SetNamespace("non-default")
157 err := getClient().Create(ctx, dep)
158 Expect(err).To(HaveOccurred())
159 })
160 It("should create a cluster scoped object", func() {
161 cr := &rbacv1.ClusterRole{
162 ObjectMeta: metav1.ObjectMeta{
163 Name: fmt.Sprintf("clusterRole-%v", count),
164 Labels: map[string]string{"name": fmt.Sprintf("clusterRole-%v", count)},
165 },
166 }
167 cr.SetGroupVersionKind(schema.GroupVersionKind{
168 Group: "rbac.authorization.k8s.io",
169 Version: "v1",
170 Kind: "ClusterRole",
171 })
172
173 By("creating the object initially")
174 err := getClient().Create(ctx, cr)
175 Expect(err).NotTo(HaveOccurred())
176
177 By("checking if the object was created")
178 res, err := clientset.RbacV1().ClusterRoles().Get(ctx, cr.Name, metav1.GetOptions{})
179 Expect(err).NotTo(HaveOccurred())
180 Expect(res).NotTo(BeNil())
181
182
183 deleteClusterRole(ctx, cr)
184 })
185 })
186
187 Describe("Update", func() {
188 var err error
189 BeforeEach(func() {
190 dep, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})
191 dep.Annotations = map[string]string{"foo": "bar"}
192 Expect(err).NotTo(HaveOccurred())
193 })
194 AfterEach(func() {
195 deleteDeployment(ctx, dep, ns)
196 })
197
198 It("should successfully update the provided object", func() {
199 By("updating the Deployment")
200 err = getClient().Update(ctx, dep)
201 Expect(err).NotTo(HaveOccurred())
202
203 By("validating if the updated Deployment has new annotation")
204 actual, err := clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{})
205 Expect(err).NotTo(HaveOccurred())
206 Expect(actual).NotTo(BeNil())
207 Expect(actual.GetNamespace()).To(Equal(ns))
208 Expect(actual.Annotations["foo"]).To(Equal("bar"))
209 })
210
211 It("should successfully update the provided object when namespace is not provided", func() {
212 By("updating the Deployment")
213 dep.SetNamespace("")
214 err = getClient().Update(ctx, dep)
215 Expect(err).NotTo(HaveOccurred())
216
217 By("validating if the updated Deployment has new annotation")
218 actual, err := clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{})
219 Expect(err).NotTo(HaveOccurred())
220 Expect(actual).NotTo(BeNil())
221 Expect(actual.GetNamespace()).To(Equal(ns))
222 Expect(actual.Annotations["foo"]).To(Equal("bar"))
223 })
224
225 It("should not update when object namespace is different", func() {
226 By("updating the Deployment")
227 dep.SetNamespace("non-default")
228 err = getClient().Update(ctx, dep)
229 Expect(err).To(HaveOccurred())
230 })
231
232 It("should not update any object from other namespace", func() {
233 By("creating a new namespace")
234 tns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "non-default-1"}}
235 _, err := clientset.CoreV1().Namespaces().Create(ctx, tns, metav1.CreateOptions{})
236 Expect(err).NotTo(HaveOccurred())
237
238 changedDep := &appsv1.Deployment{
239 ObjectMeta: metav1.ObjectMeta{
240 Name: "changed-dep",
241 Namespace: tns.Name,
242 Labels: map[string]string{"name": "changed-dep"},
243 },
244 Spec: appsv1.DeploymentSpec{
245 Replicas: &replicaCount,
246 Selector: &metav1.LabelSelector{
247 MatchLabels: map[string]string{"foo": "bar"},
248 },
249 Template: corev1.PodTemplateSpec{
250 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
251 Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
252 },
253 },
254 }
255 changedDep.Annotations = map[string]string{"foo": "bar"}
256
257 By("creating the object initially")
258 _, err = clientset.AppsV1().Deployments(tns.Name).Create(ctx, changedDep, metav1.CreateOptions{})
259 Expect(err).NotTo(HaveOccurred())
260
261 By("updating the object")
262 err = getClient().Update(ctx, changedDep)
263 Expect(err).To(HaveOccurred())
264
265 deleteDeployment(ctx, changedDep, tns.Name)
266 deleteNamespace(ctx, tns)
267 })
268
269 It("should update a cluster scoped resource", func() {
270 changedCR := &rbacv1.ClusterRole{
271 ObjectMeta: metav1.ObjectMeta{
272 Name: fmt.Sprintf("clusterRole-%v", count),
273 Labels: map[string]string{"name": fmt.Sprintf("clusterRole-%v", count)},
274 },
275 }
276
277 changedCR.SetGroupVersionKind(schema.GroupVersionKind{
278 Group: "rbac.authorization.k8s.io",
279 Version: "v1",
280 Kind: "ClusterRole",
281 })
282
283 By("Setting annotations and creating the resource")
284 changedCR.Annotations = map[string]string{"foo": "bar"}
285 changedCR, err = clientset.RbacV1().ClusterRoles().Create(ctx, changedCR, metav1.CreateOptions{})
286 Expect(err).NotTo(HaveOccurred())
287
288 By("updating the deployment")
289 err = getClient().Update(ctx, changedCR)
290
291 By("validating if the cluster role was update")
292 actual, err := clientset.RbacV1().ClusterRoles().Get(ctx, changedCR.Name, metav1.GetOptions{})
293 Expect(err).NotTo(HaveOccurred())
294 Expect(actual).NotTo(BeNil())
295 Expect(actual.Annotations["foo"]).To(Equal("bar"))
296
297
298 deleteClusterRole(ctx, changedCR)
299 })
300
301 })
302
303 Describe("Patch", func() {
304 var err error
305 BeforeEach(func() {
306 dep, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})
307 Expect(err).NotTo(HaveOccurred())
308 })
309
310 AfterEach(func() {
311 deleteDeployment(ctx, dep, ns)
312 })
313
314 It("should successfully modify the object using Patch", func() {
315 By("Applying Patch")
316 err = getClient().Patch(ctx, dep, client.RawPatch(types.MergePatchType, generatePatch()))
317 Expect(err).NotTo(HaveOccurred())
318
319 By("validating patched Deployment has new annotations")
320 actual, err := clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{})
321 Expect(err).NotTo(HaveOccurred())
322 Expect(actual.Annotations["foo"]).To(Equal("bar"))
323 Expect(actual.GetNamespace()).To(Equal(ns))
324 })
325
326 It("should successfully modify the object using Patch when namespace is not provided", func() {
327 By("Applying Patch")
328 dep.SetNamespace("")
329 err = getClient().Patch(ctx, dep, client.RawPatch(types.MergePatchType, generatePatch()))
330 Expect(err).NotTo(HaveOccurred())
331
332 By("validating patched Deployment has new annotations")
333 actual, err := clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{})
334 Expect(err).NotTo(HaveOccurred())
335 Expect(actual.Annotations["foo"]).To(Equal("bar"))
336 Expect(actual.GetNamespace()).To(Equal(ns))
337 })
338
339 It("should not modify the object when namespace of the object is different", func() {
340 dep.SetNamespace("non-default")
341 err = getClient().Patch(ctx, dep, client.RawPatch(types.MergePatchType, generatePatch()))
342 Expect(err).To(HaveOccurred())
343 })
344
345 It("should not modify an object from a different namespace", func() {
346 By("creating a new namespace")
347 tns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "non-default-2"}}
348 _, err := clientset.CoreV1().Namespaces().Create(ctx, tns, metav1.CreateOptions{})
349 Expect(err).NotTo(HaveOccurred())
350
351 changedDep := &appsv1.Deployment{
352 ObjectMeta: metav1.ObjectMeta{
353 Name: "changed-dep",
354 Namespace: tns.Name,
355 Labels: map[string]string{"name": "changed-dep"},
356 },
357 Spec: appsv1.DeploymentSpec{
358 Replicas: &replicaCount,
359 Selector: &metav1.LabelSelector{
360 MatchLabels: map[string]string{"foo": "bar"},
361 },
362 Template: corev1.PodTemplateSpec{
363 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
364 Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
365 },
366 },
367 }
368
369 By("creating the object initially")
370 changedDep, err = clientset.AppsV1().Deployments(tns.Name).Create(ctx, changedDep, metav1.CreateOptions{})
371 Expect(err).NotTo(HaveOccurred())
372
373 err = getClient().Patch(ctx, changedDep, client.RawPatch(types.MergePatchType, generatePatch()))
374 Expect(err).To(HaveOccurred())
375
376 deleteDeployment(ctx, changedDep, tns.Name)
377 deleteNamespace(ctx, tns)
378 })
379
380 It("should successfully modify cluster scoped resource", func() {
381 cr := &rbacv1.ClusterRole{
382 ObjectMeta: metav1.ObjectMeta{
383 Name: fmt.Sprintf("clusterRole-%v", count),
384 Labels: map[string]string{"name": fmt.Sprintf("clusterRole-%v", count)},
385 },
386 }
387
388 cr.SetGroupVersionKind(schema.GroupVersionKind{
389 Group: "rbac.authorization.k8s.io",
390 Version: "v1",
391 Kind: "ClusterRole",
392 })
393
394 By("creating the resource")
395 cr, err = clientset.RbacV1().ClusterRoles().Create(ctx, cr, metav1.CreateOptions{})
396 Expect(err).ToNot(HaveOccurred())
397
398 By("Applying Patch")
399 err = getClient().Patch(ctx, cr, client.RawPatch(types.MergePatchType, generatePatch()))
400 Expect(err).NotTo(HaveOccurred())
401
402 By("Validating the patch")
403 actual, err := clientset.RbacV1().ClusterRoles().Get(ctx, cr.Name, metav1.GetOptions{})
404 Expect(err).NotTo(HaveOccurred())
405 Expect(actual.Annotations["foo"]).To(Equal("bar"))
406
407
408 deleteClusterRole(ctx, cr)
409 })
410 })
411
412 Describe("Delete and DeleteAllOf", func() {
413 var err error
414 BeforeEach(func() {
415 dep, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})
416 Expect(err).NotTo(HaveOccurred())
417 })
418
419 AfterEach(func() {
420 deleteDeployment(ctx, dep, ns)
421 })
422 It("should successfully delete an object when namespace is not specified", func() {
423 By("deleting the object")
424 dep.SetNamespace("")
425 err = getClient().Delete(ctx, dep)
426 Expect(err).NotTo(HaveOccurred())
427
428 By("validating the Deployment no longer exists")
429 _, err = clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{})
430 Expect(err).To(HaveOccurred())
431 })
432
433 It("should successfully delete all of the deployments in the given namespace", func() {
434 By("Deleting all objects in the namespace")
435 err = getClient().DeleteAllOf(ctx, dep)
436 Expect(err).NotTo(HaveOccurred())
437
438 By("validating the Deployment no longer exists")
439 _, err = clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{})
440 Expect(err).To(HaveOccurred())
441 })
442
443 It("should not delete deployments in other namespaces", func() {
444 tns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "non-default-3"}}
445 _, err = clientset.CoreV1().Namespaces().Create(ctx, tns, metav1.CreateOptions{})
446 Expect(err).NotTo(HaveOccurred())
447
448 changedDep := &appsv1.Deployment{
449 ObjectMeta: metav1.ObjectMeta{
450 Name: "changed-dep",
451 Namespace: tns.Name,
452 Labels: map[string]string{"name": "changed-dep"},
453 },
454 Spec: appsv1.DeploymentSpec{
455 Replicas: &replicaCount,
456 Selector: &metav1.LabelSelector{
457 MatchLabels: map[string]string{"foo": "bar"},
458 },
459 Template: corev1.PodTemplateSpec{
460 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
461 Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
462 },
463 },
464 }
465
466 By("creating the object initially in other namespace")
467 changedDep, err = clientset.AppsV1().Deployments(tns.Name).Create(ctx, changedDep, metav1.CreateOptions{})
468 Expect(err).NotTo(HaveOccurred())
469
470 err = getClient().DeleteAllOf(ctx, dep)
471 Expect(err).NotTo(HaveOccurred())
472
473 By("validating the Deployment exists")
474 actual, err := clientset.AppsV1().Deployments(tns.Name).Get(ctx, changedDep.Name, metav1.GetOptions{})
475 Expect(err).NotTo(HaveOccurred())
476 Expect(actual).To(BeEquivalentTo(changedDep))
477
478 deleteDeployment(ctx, changedDep, tns.Name)
479 deleteNamespace(ctx, tns)
480 })
481 })
482
483 Describe("SubResourceWriter", func() {
484 var err error
485 BeforeEach(func() {
486 dep, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})
487 Expect(err).NotTo(HaveOccurred())
488 })
489
490 AfterEach(func() {
491 deleteDeployment(ctx, dep, ns)
492 })
493
494 It("should change objects via update status", func() {
495 changedDep := dep.DeepCopy()
496 changedDep.Status.Replicas = 99
497
498 Expect(getClient().SubResource("status").Update(ctx, changedDep)).NotTo(HaveOccurred())
499
500 actual, err := clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{})
501 Expect(err).NotTo(HaveOccurred())
502 Expect(actual).NotTo(BeNil())
503 Expect(actual.GetNamespace()).To(BeEquivalentTo(ns))
504 Expect(actual.Status.Replicas).To(BeEquivalentTo(99))
505 })
506
507 It("should not change objects via update status when object namespace is different", func() {
508 changedDep := dep.DeepCopy()
509 changedDep.SetNamespace("test")
510 changedDep.Status.Replicas = 99
511
512 Expect(getClient().SubResource("status").Update(ctx, changedDep)).To(HaveOccurred())
513 })
514
515 It("should change objects via status patch", func() {
516 changedDep := dep.DeepCopy()
517 changedDep.Status.Replicas = 99
518
519 Expect(getClient().SubResource("status").Patch(ctx, changedDep, client.MergeFrom(dep))).NotTo(HaveOccurred())
520
521 actual, err := clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{})
522 Expect(err).NotTo(HaveOccurred())
523 Expect(actual).NotTo(BeNil())
524 Expect(actual.GetNamespace()).To(BeEquivalentTo(ns))
525 Expect(actual.Status.Replicas).To(BeEquivalentTo(99))
526 })
527
528 It("should not change objects via status patch when object namespace is different", func() {
529 changedDep := dep.DeepCopy()
530 changedDep.Status.Replicas = 99
531 changedDep.SetNamespace("test")
532
533 Expect(getClient().SubResource("status").Patch(ctx, changedDep, client.MergeFrom(dep))).To(HaveOccurred())
534 })
535 })
536
537 Describe("Test on invalid objects", func() {
538 It("should refuse to perform operations on invalid object", func() {
539 err := getClient().Create(ctx, nil)
540 Expect(err).To(HaveOccurred())
541
542 err = getClient().List(ctx, nil)
543 Expect(err).To(HaveOccurred())
544
545 err = getClient().Patch(ctx, nil, client.MergeFrom(dep))
546 Expect(err).To(HaveOccurred())
547
548 err = getClient().Update(ctx, nil)
549 Expect(err).To(HaveOccurred())
550
551 err = getClient().Delete(ctx, nil)
552 Expect(err).To(HaveOccurred())
553
554 err = getClient().Status().Patch(ctx, nil, client.MergeFrom(dep))
555 Expect(err).To(HaveOccurred())
556
557 err = getClient().Status().Update(ctx, nil)
558 Expect(err).To(HaveOccurred())
559
560 })
561
562 })
563 })
564
565 func generatePatch() []byte {
566 mergePatch, err := json.Marshal(map[string]interface{}{
567 "metadata": map[string]interface{}{
568 "annotations": map[string]interface{}{
569 "foo": "bar",
570 },
571 },
572 })
573 Expect(err).NotTo(HaveOccurred())
574 return mergePatch
575 }
576
577 func deleteClusterRole(ctx context.Context, cr *rbacv1.ClusterRole) {
578 _, err := clientset.RbacV1().ClusterRoles().Get(ctx, cr.Name, metav1.GetOptions{})
579 if err == nil {
580 err = clientset.RbacV1().ClusterRoles().Delete(ctx, cr.Name, metav1.DeleteOptions{})
581 Expect(err).NotTo(HaveOccurred())
582 }
583 }
584
View as plain text