sigs.k8s.io/controller-runtime@v0.18.2/pkg/client/namespaced_client_test.go (about) 1 /* 2 Copyright 2020 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 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 // Delete the clusterRole Resource 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 // delete cluster role resource 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 // delete the resource 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 }