From abe7bd5f9dfae3a10e72544350274cb27564261d Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Sun, 2 Mar 2025 14:52:21 +0100 Subject: [PATCH 1/3] Use watchList in typed and unstructured client if WatchListClient is enabled --- pkg/client/client_test.go | 2777 ++++++++++++++------------ pkg/client/dryrun_test.go | 2 +- pkg/client/namespaced_client_test.go | 26 +- pkg/client/typed_client.go | 18 + pkg/client/unstructured_client.go | 17 + pkg/client/watch_test.go | 2 +- 6 files changed, 1592 insertions(+), 1250 deletions(-) diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index ff014e7321..ef4bd75c75 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -24,10 +24,12 @@ import ( "errors" "fmt" "reflect" + "strconv" "strings" "sync/atomic" "time" + "github.com/go-logr/logr/funcr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" @@ -43,16 +45,20 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + clientfeatures "k8s.io/client-go/features" + "k8s.io/client-go/kubernetes" kscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/examples/crd/pkg" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" + pkglog "sigs.k8s.io/controller-runtime/pkg/log" ) -func deleteDeployment(ctx context.Context, dep *appsv1.Deployment, ns string) { +func deleteDeployment(ctx context.Context, clientset *kubernetes.Clientset, dep *appsv1.Deployment, ns string) { _, err := clientset.AppsV1().Deployments(ns).Get(ctx, dep.Name, metav1.GetOptions{}) if err == nil { err = clientset.AppsV1().Deployments(ns).Delete(ctx, dep.Name, metav1.DeleteOptions{}) @@ -60,7 +66,7 @@ func deleteDeployment(ctx context.Context, dep *appsv1.Deployment, ns string) { } } -func deleteNamespace(ctx context.Context, ns *corev1.Namespace) { +func deleteNamespace(ctx context.Context, clientset *kubernetes.Clientset, ns *corev1.Namespace) { ns, err := clientset.CoreV1().Namespaces().Get(ctx, ns.Name, metav1.GetOptions{}) if err != nil { return @@ -217,7 +223,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC GracePeriodSeconds: &zero, PropagationPolicy: &policy, } - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) _, err := clientset.CoreV1().Nodes().Get(ctx, node.Name, metav1.GetOptions{}) if err == nil { err = clientset.CoreV1().Nodes().Delete(ctx, node.Name, *delOptions) @@ -247,7 +253,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC tns, err = clientset.CoreV1().Namespaces().Create(ctx, tns, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) Expect(tns).NotTo(BeNil()) - defer deleteNamespace(ctx, tns) + defer deleteNamespace(ctx, clientset, tns) toCreate := &pkg.ChaosPod{ ObjectMeta: metav1.ObjectMeta{ @@ -2199,1464 +2205,1765 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) }) - Describe("List", func() { - Context("with structured objects", func() { - It("should fetch collection of objects", func() { - By("creating an initial object") - dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) - - By("listing all objects of that type in the cluster") - deps := &appsv1.DeploymentList{} - Expect(cl.List(context.Background(), deps)).NotTo(HaveOccurred()) + Describe("CreateOptions", func() { + It("should allow setting DryRun to 'all'", func() { + co := &client.CreateOptions{} + client.DryRunAll.ApplyToCreate(co) + all := []string{metav1.DryRunAll} + Expect(co.AsCreateOptions().DryRun).To(Equal(all)) + }) - Expect(deps.Items).NotTo(BeEmpty()) - hasDep := false - for _, item := range deps.Items { - if item.Name == dep.Name && item.Namespace == dep.Namespace { - hasDep = true - break - } - } - Expect(hasDep).To(BeTrue()) - }) + It("should allow setting the field manager", func() { + po := &client.CreateOptions{} + client.FieldOwner("some-owner").ApplyToCreate(po) + Expect(po.AsCreateOptions().FieldManager).To(Equal("some-owner")) + }) - It("should fetch unstructured collection of objects", func() { - By("create an initial object") - _, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + It("should produce empty metav1.CreateOptions if nil", func() { + var co *client.CreateOptions + Expect(co.AsCreateOptions()).To(Equal(&metav1.CreateOptions{})) + co = &client.CreateOptions{} + Expect(co.AsCreateOptions()).To(Equal(&metav1.CreateOptions{})) + }) + }) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + Describe("DeleteOptions", func() { + It("should allow setting GracePeriodSeconds", func() { + do := &client.DeleteOptions{} + client.GracePeriodSeconds(1).ApplyToDelete(do) + gp := int64(1) + Expect(do.AsDeleteOptions().GracePeriodSeconds).To(Equal(&gp)) + }) - By("listing all objects of that type in the cluster") - deps := &unstructured.UnstructuredList{} - deps.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Kind: "DeploymentList", - Version: "v1", - }) - err = cl.List(context.Background(), deps) - Expect(err).NotTo(HaveOccurred()) - - Expect(deps.Items).NotTo(BeEmpty()) - hasDep := false - for _, item := range deps.Items { - Expect(item.GroupVersionKind()).To(Equal(schema.GroupVersionKind{ - Group: "apps", - Kind: "Deployment", - Version: "v1", - })) - if item.GetName() == dep.Name && item.GetNamespace() == dep.Namespace { - hasDep = true - break - } - } - Expect(hasDep).To(BeTrue()) - }) + It("should allow setting Precondition", func() { + do := &client.DeleteOptions{} + pc := metav1.NewUIDPreconditions("uid") + client.Preconditions(*pc).ApplyToDelete(do) + Expect(do.AsDeleteOptions().Preconditions).To(Equal(pc)) + Expect(do.Preconditions).To(Equal(pc)) + }) - It("should fetch unstructured collection of objects, even if scheme is empty", func() { - By("create an initial object") - _, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + It("should allow setting PropagationPolicy", func() { + do := &client.DeleteOptions{} + client.PropagationPolicy(metav1.DeletePropagationForeground).ApplyToDelete(do) + dp := metav1.DeletePropagationForeground + Expect(do.AsDeleteOptions().PropagationPolicy).To(Equal(&dp)) + }) - cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) - Expect(err).NotTo(HaveOccurred()) + It("should allow setting DryRun", func() { + do := &client.DeleteOptions{} + client.DryRunAll.ApplyToDelete(do) + all := []string{metav1.DryRunAll} + Expect(do.AsDeleteOptions().DryRun).To(Equal(all)) + }) - By("listing all objects of that type in the cluster") - deps := &unstructured.UnstructuredList{} - deps.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Kind: "DeploymentList", - Version: "v1", - }) - err = cl.List(context.Background(), deps) - Expect(err).NotTo(HaveOccurred()) + It("should produce empty metav1.DeleteOptions if nil", func() { + var do *client.DeleteOptions + Expect(do.AsDeleteOptions()).To(Equal(&metav1.DeleteOptions{})) + do = &client.DeleteOptions{} + Expect(do.AsDeleteOptions()).To(Equal(&metav1.DeleteOptions{})) + }) - Expect(deps.Items).NotTo(BeEmpty()) - hasDep := false - for _, item := range deps.Items { - if item.GetName() == dep.Name && item.GetNamespace() == dep.Namespace { - hasDep = true - break - } - } - Expect(hasDep).To(BeTrue()) + It("should merge multiple options together", func() { + gp := int64(1) + pc := metav1.NewUIDPreconditions("uid") + dp := metav1.DeletePropagationForeground + do := &client.DeleteOptions{} + do.ApplyOptions([]client.DeleteOption{ + client.GracePeriodSeconds(gp), + client.Preconditions(*pc), + client.PropagationPolicy(dp), }) + Expect(do.GracePeriodSeconds).To(Equal(&gp)) + Expect(do.Preconditions).To(Equal(pc)) + Expect(do.PropagationPolicy).To(Equal(&dp)) + }) + }) - It("should return an empty list if there are no matching objects", func() { - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) - - By("listing all Deployments in the cluster") - deps := &appsv1.DeploymentList{} - Expect(cl.List(context.Background(), deps)).NotTo(HaveOccurred()) - - By("validating no Deployments are returned") - Expect(deps.Items).To(BeEmpty()) + Describe("DeleteCollectionOptions", func() { + It("should be convertable to list options", func() { + gp := int64(1) + do := &client.DeleteAllOfOptions{} + do.ApplyOptions([]client.DeleteAllOfOption{ + client.GracePeriodSeconds(gp), + client.MatchingLabels{"foo": "bar"}, }) - // TODO(seans): get label selector test working - It("should filter results by label selector", func() { - By("creating a Deployment with the app=frontend label") - depFrontend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-frontend", - Namespace: ns, - Labels: map[string]string{"app": "frontend"}, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - By("creating a Deployment with the app=backend label") - depBackend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-backend", - Namespace: ns, - Labels: map[string]string{"app": "backend"}, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) - - By("listing all Deployments with label app=backend") - deps := &appsv1.DeploymentList{} - labels := map[string]string{"app": "backend"} - err = cl.List(context.Background(), deps, client.MatchingLabels(labels)) - Expect(err).NotTo(HaveOccurred()) - - By("only the Deployment with the backend label is returned") - Expect(deps.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(deps.Items))) - actual := deps.Items[0] - Expect(actual.Name).To(Equal("deployment-backend")) + listOpts := do.AsListOptions() + Expect(listOpts).NotTo(BeNil()) + Expect(listOpts.LabelSelector).To(Equal("foo=bar")) + }) - deleteDeployment(ctx, depFrontend, ns) - deleteDeployment(ctx, depBackend, ns) + It("should be convertable to delete options", func() { + gp := int64(1) + do := &client.DeleteAllOfOptions{} + do.ApplyOptions([]client.DeleteAllOfOption{ + client.GracePeriodSeconds(gp), + client.MatchingLabels{"foo": "bar"}, }) - It("should filter results by namespace selector", func() { - By("creating a Deployment in test-namespace-1") - tns1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-1"}} - _, err := clientset.CoreV1().Namespaces().Create(ctx, tns1, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depFrontend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: "test-namespace-1"}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - depFrontend, err = clientset.AppsV1().Deployments("test-namespace-1").Create(ctx, depFrontend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - By("creating a Deployment in test-namespace-2") - tns2 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-2"}} - _, err = clientset.CoreV1().Namespaces().Create(ctx, tns2, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depBackend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: "test-namespace-2"}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - depBackend, err = clientset.AppsV1().Deployments("test-namespace-2").Create(ctx, depBackend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + deleteOpts := do.AsDeleteOptions() + Expect(deleteOpts).NotTo(BeNil()) + Expect(deleteOpts.GracePeriodSeconds).To(Equal(&gp)) + }) + }) - By("listing all Deployments in test-namespace-1") - deps := &appsv1.DeploymentList{} - err = cl.List(context.Background(), deps, client.InNamespace("test-namespace-1")) - Expect(err).NotTo(HaveOccurred()) + Describe("GetOptions", func() { + It("should be convertable to metav1.GetOptions", func() { + o := (&client.GetOptions{}).ApplyOptions([]client.GetOption{ + &client.GetOptions{Raw: &metav1.GetOptions{ResourceVersion: "RV0"}}, + }) + mo := o.AsGetOptions() + Expect(mo).NotTo(BeNil()) + Expect(mo.ResourceVersion).To(Equal("RV0")) + }) - By("only the Deployment in test-namespace-1 is returned") - Expect(deps.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(deps.Items))) - actual := deps.Items[0] - Expect(actual.Name).To(Equal("deployment-frontend")) + It("should produce empty metav1.GetOptions if nil", func() { + var o *client.GetOptions + Expect(o.AsGetOptions()).To(Equal(&metav1.GetOptions{})) + o = &client.GetOptions{} + Expect(o.AsGetOptions()).To(Equal(&metav1.GetOptions{})) + }) + }) - deleteDeployment(ctx, depFrontend, "test-namespace-1") - deleteDeployment(ctx, depBackend, "test-namespace-2") - deleteNamespace(ctx, tns1) - deleteNamespace(ctx, tns2) + Describe("ListOptions", func() { + It("should be convertable to metav1.ListOptions", func() { + lo := (&client.ListOptions{}).ApplyOptions([]client.ListOption{ + client.MatchingFields{"field1": "bar"}, + client.InNamespace("test-namespace"), + client.MatchingLabels{"foo": "bar"}, + client.Limit(1), + client.Continue("foo"), }) + mlo := lo.AsListOptions() + Expect(mlo).NotTo(BeNil()) + Expect(mlo.LabelSelector).To(Equal("foo=bar")) + Expect(mlo.FieldSelector).To(Equal("field1=bar")) + Expect(mlo.Limit).To(Equal(int64(1))) + Expect(mlo.Continue).To(Equal("foo")) + }) - It("should filter results by field selector", func() { - By("creating a Deployment with name deployment-frontend") - depFrontend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: ns}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + It("should be populated by MatchingLabels", func() { + lo := &client.ListOptions{} + client.MatchingLabels{"foo": "bar"}.ApplyToList(lo) + Expect(lo).NotTo(BeNil()) + Expect(lo.LabelSelector.String()).To(Equal("foo=bar")) + }) - By("creating a Deployment with name deployment-backend") - depBackend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: ns}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + It("should be populated by MatchingField", func() { + lo := &client.ListOptions{} + client.MatchingFields{"field1": "bar"}.ApplyToList(lo) + Expect(lo).NotTo(BeNil()) + Expect(lo.FieldSelector.String()).To(Equal("field1=bar")) + }) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + It("should be populated by InNamespace", func() { + lo := &client.ListOptions{} + client.InNamespace("test").ApplyToList(lo) + Expect(lo).NotTo(BeNil()) + Expect(lo.Namespace).To(Equal("test")) + }) - By("listing all Deployments with field metadata.name=deployment-backend") - deps := &appsv1.DeploymentList{} - err = cl.List(context.Background(), deps, - client.MatchingFields{"metadata.name": "deployment-backend"}) - Expect(err).NotTo(HaveOccurred()) + It("should produce empty metav1.ListOptions if nil", func() { + var do *client.ListOptions + Expect(do.AsListOptions()).To(Equal(&metav1.ListOptions{})) + do = &client.ListOptions{} + Expect(do.AsListOptions()).To(Equal(&metav1.ListOptions{})) + }) - By("only the Deployment with the backend field is returned") - Expect(deps.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(deps.Items))) - actual := deps.Items[0] - Expect(actual.Name).To(Equal("deployment-backend")) + It("should be populated by Limit", func() { + lo := &client.ListOptions{} + client.Limit(1).ApplyToList(lo) + Expect(lo).NotTo(BeNil()) + Expect(lo.Limit).To(Equal(int64(1))) + }) - deleteDeployment(ctx, depFrontend, ns) - deleteDeployment(ctx, depBackend, ns) + It("should ignore Limit when converted to metav1.ListOptions and watch is true", func() { + lo := &client.ListOptions{ + Raw: &metav1.ListOptions{Watch: true}, + } + lo.ApplyOptions([]client.ListOption{ + client.Limit(1), }) + mlo := lo.AsListOptions() + Expect(mlo).NotTo(BeNil()) + Expect(mlo.Limit).To(BeZero()) + }) - It("should filter results by namespace selector and label selector", func() { - By("creating a Deployment in test-namespace-3 with the app=frontend label") - tns3 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-3"}} - _, err := clientset.CoreV1().Namespaces().Create(ctx, tns3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depFrontend3 := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-frontend", - Namespace: "test-namespace-3", - Labels: map[string]string{"app": "frontend"}, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - depFrontend3, err = clientset.AppsV1().Deployments("test-namespace-3").Create(ctx, depFrontend3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - By("creating a Deployment in test-namespace-3 with the app=backend label") - depBackend3 := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-backend", - Namespace: "test-namespace-3", - Labels: map[string]string{"app": "backend"}, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - depBackend3, err = clientset.AppsV1().Deployments("test-namespace-3").Create(ctx, depBackend3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + It("should be populated by Continue", func() { + lo := &client.ListOptions{} + client.Continue("foo").ApplyToList(lo) + Expect(lo).NotTo(BeNil()) + Expect(lo.Continue).To(Equal("foo")) + }) - By("creating a Deployment in test-namespace-4 with the app=frontend label") - tns4 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-4"}} - _, err = clientset.CoreV1().Namespaces().Create(ctx, tns4, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depFrontend4 := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-frontend", - Namespace: "test-namespace-4", - Labels: map[string]string{"app": "frontend"}, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - depFrontend4, err = clientset.AppsV1().Deployments("test-namespace-4").Create(ctx, depFrontend4, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + It("should ignore Continue token when converted to metav1.ListOptions and watch is true", func() { + lo := &client.ListOptions{ + Raw: &metav1.ListOptions{Watch: true}, + } + lo.ApplyOptions([]client.ListOption{ + client.Continue("foo"), + }) + mlo := lo.AsListOptions() + Expect(mlo).NotTo(BeNil()) + Expect(mlo.Continue).To(BeEmpty()) + }) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + It("should ignore both Limit and Continue token when converted to metav1.ListOptions and watch is true", func() { + lo := &client.ListOptions{ + Raw: &metav1.ListOptions{Watch: true}, + } + lo.ApplyOptions([]client.ListOption{ + client.Limit(1), + client.Continue("foo"), + }) + mlo := lo.AsListOptions() + Expect(mlo).NotTo(BeNil()) + Expect(mlo.Limit).To(BeZero()) + Expect(mlo.Continue).To(BeEmpty()) + }) + }) - By("listing all Deployments in test-namespace-3 with label app=frontend") - deps := &appsv1.DeploymentList{} - labels := map[string]string{"app": "frontend"} - err = cl.List(context.Background(), deps, - client.InNamespace("test-namespace-3"), - client.MatchingLabels(labels), - ) - Expect(err).NotTo(HaveOccurred()) + Describe("UpdateOptions", func() { + It("should allow setting DryRun to 'all'", func() { + uo := &client.UpdateOptions{} + client.DryRunAll.ApplyToUpdate(uo) + all := []string{metav1.DryRunAll} + Expect(uo.AsUpdateOptions().DryRun).To(Equal(all)) + }) - By("only the Deployment in test-namespace-3 with label app=frontend is returned") - Expect(deps.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(deps.Items))) - actual := deps.Items[0] - Expect(actual.Name).To(Equal("deployment-frontend")) - Expect(actual.Namespace).To(Equal("test-namespace-3")) + It("should allow setting the field manager", func() { + po := &client.UpdateOptions{} + client.FieldOwner("some-owner").ApplyToUpdate(po) + Expect(po.AsUpdateOptions().FieldManager).To(Equal("some-owner")) + }) - deleteDeployment(ctx, depFrontend3, "test-namespace-3") - deleteDeployment(ctx, depBackend3, "test-namespace-3") - deleteDeployment(ctx, depFrontend4, "test-namespace-4") - deleteNamespace(ctx, tns3) - deleteNamespace(ctx, tns4) - }) + It("should produce empty metav1.UpdateOptions if nil", func() { + var co *client.UpdateOptions + Expect(co.AsUpdateOptions()).To(Equal(&metav1.UpdateOptions{})) + co = &client.UpdateOptions{} + Expect(co.AsUpdateOptions()).To(Equal(&metav1.UpdateOptions{})) + }) + }) - It("should filter results using limit and continue options", func() { + Describe("PatchOptions", func() { + It("should allow setting DryRun to 'all'", func() { + po := &client.PatchOptions{} + client.DryRunAll.ApplyToPatch(po) + all := []string{metav1.DryRunAll} + Expect(po.AsPatchOptions().DryRun).To(Equal(all)) + }) - makeDeployment := func(suffix string) *appsv1.Deployment { - return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("deployment-%s", suffix), - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"foo": "bar"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - } + It("should allow setting Force to 'true'", func() { + po := &client.PatchOptions{} + client.ForceOwnership.ApplyToPatch(po) + mpo := po.AsPatchOptions() + Expect(mpo.Force).NotTo(BeNil()) + Expect(*mpo.Force).To(BeTrue()) + }) - By("creating 4 deployments") - dep1 := makeDeployment("1") - dep1, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep1, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - defer deleteDeployment(ctx, dep1, ns) + It("should allow setting the field manager", func() { + po := &client.PatchOptions{} + client.FieldOwner("some-owner").ApplyToPatch(po) + Expect(po.AsPatchOptions().FieldManager).To(Equal("some-owner")) + }) - dep2 := makeDeployment("2") - dep2, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep2, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - defer deleteDeployment(ctx, dep2, ns) + It("should produce empty metav1.PatchOptions if nil", func() { + var po *client.PatchOptions + Expect(po.AsPatchOptions()).To(Equal(&metav1.PatchOptions{})) + po = &client.PatchOptions{} + Expect(po.AsPatchOptions()).To(Equal(&metav1.PatchOptions{})) + }) + }) +}) - dep3 := makeDeployment("3") - dep3, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - defer deleteDeployment(ctx, dep3, ns) +var _ = Describe("Client List", func() { + Describe("(WatchList: true, WatchListClient: true)", Ordered, ContinueOnFailure, func() { + ClientListTest(true, true) + }) + Describe("(WatchList: false, WatchListClient: true", Ordered, ContinueOnFailure, func() { + ClientListTest(false, true) + }) + Describe("(WatchList: true, WatchListClient: false)", Ordered, ContinueOnFailure, func() { + ClientListTest(true, false) + }) + Describe("(WatchList: false, WatchListClient: false)", Ordered, ContinueOnFailure, func() { + ClientListTest(false, false) + }) +}) - dep4 := makeDeployment("4") - dep4, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep4, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - defer deleteDeployment(ctx, dep4, ns) +func ClientListTest(watchListEnabled, watchListClientEnabled bool) { + var testenv *envtest.Environment + var cfg *rest.Config + var clientset *kubernetes.Clientset - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + var dep *appsv1.Deployment + var count uint64 + var ns = "default" - By("listing 1 deployment when limit=1 is used") - deps := &appsv1.DeploymentList{} - err = cl.List(context.Background(), deps, - client.Limit(1), - ) - Expect(err).NotTo(HaveOccurred()) + ctx := context.TODO() - Expect(deps.Items).To(HaveLen(1)) - Expect(deps.Continue).NotTo(BeEmpty()) - Expect(deps.Items[0].Name).To(Equal(dep1.Name)) + BeforeAll(func() { + testenv = &envtest.Environment{ + CRDDirectoryPaths: []string{"./testdata"}, + } + // Enable/disable WatchList server-side feature gate. + testenv.ControlPlane.GetAPIServer().Configure().Append("feature-gates", "WatchList="+strconv.FormatBool(watchListEnabled)) + // Enable/disable WatchList client-side feature gate. + featureGates := clientfeatures.FeatureGates().(interface { + Set(featureName clientfeatures.Feature, featureValue bool) error + Enabled(key clientfeatures.Feature) bool + }) + watchListClientEnabledBefore := featureGates.Enabled(clientfeatures.WatchListClient) + Expect(featureGates.Set(clientfeatures.WatchListClient, watchListClientEnabled)).To(Succeed()) + DeferCleanup(func() { + Expect(featureGates.Set(clientfeatures.WatchListClient, watchListClientEnabledBefore)).To(Succeed()) + }) - continueToken := deps.Continue + var err error + cfg, err = testenv.Start() + Expect(err).NotTo(HaveOccurred()) - By("listing the next deployment when previous continuation token is used and limit=1") - deps = &appsv1.DeploymentList{} - err = cl.List(context.Background(), deps, - client.Limit(1), - client.Continue(continueToken), - ) - Expect(err).NotTo(HaveOccurred()) + clientset, err = kubernetes.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred()) + }) - Expect(deps.Items).To(HaveLen(1)) - Expect(deps.Continue).NotTo(BeEmpty()) - Expect(deps.Items[0].Name).To(Equal(dep2.Name)) + AfterAll(func() { + Expect(testenv.Stop()).To(Succeed()) + }) - continueToken = deps.Continue + BeforeEach(func() { + atomic.AddUint64(&count, 1) + dep = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("deployment-name-%v", count), Namespace: ns, Labels: map[string]string{"app": fmt.Sprintf("bar-%v", count)}}, + Spec: appsv1.DeploymentSpec{ + Replicas: ptr.To[int32](2), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + }) - By("listing the 2 remaining deployments when previous continuation token is used without a limit") - deps = &appsv1.DeploymentList{} - err = cl.List(context.Background(), deps, - client.Continue(continueToken), - ) - Expect(err).NotTo(HaveOccurred()) + AfterEach(func() { + deleteDeployment(ctx, clientset, dep, ns) + }) - Expect(deps.Items).To(HaveLen(2)) - Expect(deps.Continue).To(BeEmpty()) - Expect(deps.Items[0].Name).To(Equal(dep3.Name)) - Expect(deps.Items[1].Name).To(Equal(dep4.Name)) - }) + Context("with structured objects", func() { + It("should fetch collection of objects", func() { + By("creating an initial object") + dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - PIt("should fail if the object doesn't have meta", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - }) + var logs []string + ctx := pkglog.IntoContext(context.Background(), funcr.New(func(_, msg string) { + logs = append(logs, msg) + }, funcr.Options{Verbosity: 8})) // Increase log level so we can use to determine if watchList and/or list was called. - PIt("should fail if the object cannot be mapped to a GVK", func() { + By("listing all objects of that type in the cluster") + deps := &appsv1.DeploymentList{} + Expect(cl.List(ctx, deps)).To(Succeed()) - }) + Expect(deps.Items).NotTo(BeEmpty()) + hasDep := false + for _, item := range deps.Items { + if item.Name == dep.Name && item.Namespace == dep.Namespace { + hasDep = true + break + } + } + Expect(hasDep).To(BeTrue()) + + switch { + case watchListEnabled && watchListClientEnabled: + // Expect successful watchList + // We expect that no response bodies are logged + Expect(logs).ToNot(ContainElement(MatchRegexp("Response Body.*DeploymentList"))) + case !watchListEnabled && watchListClientEnabled: + // Expect failed watchList + successful fallback to list + Expect(logs).To(ContainElements( + "\"msg\"=\"Warning: The watchlist request for deployments ended with an error, " + + "falling back to the standard LIST semantics\" " + + "\"error\"=\"ListOptions.meta.k8s.io \\\"\\\" is invalid: sendInitialEvents: " + + "Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled\"")) + // Fallback list logs response body + Expect(logs).To(ContainElement(MatchRegexp("Response Body.*DeploymentList"))) + case watchListEnabled && !watchListClientEnabled: + // Expect successful list + // List logs response body + Expect(logs).To(ContainElement(MatchRegexp("Response Body.*DeploymentList"))) + case !watchListEnabled && !watchListClientEnabled: + // Expect successful list + // List logs response body + Expect(logs).To(ContainElement(MatchRegexp("Response Body.*DeploymentList"))) + } + }) - PIt("should fail if the GVK cannot be mapped to a Resource", func() { + It("should return an empty list if there are no matching objects", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - }) - }) + By("listing all Deployments in the cluster") + deps := &appsv1.DeploymentList{} + Expect(cl.List(context.Background(), deps)).NotTo(HaveOccurred()) - Context("with unstructured objects", func() { - It("should fetch collection of objects", func() { - By("create an initial object") - _, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + By("validating no Deployments are returned") + Expect(deps.Items).To(BeEmpty()) + }) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + // TODO(seans): get label selector test working + It("should filter results by label selector", func() { + By("creating a Deployment with the app=frontend label") + depFrontend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-frontend", + Namespace: ns, + Labels: map[string]string{"app": "frontend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - By("listing all objects of that type in the cluster") - deps := &unstructured.UnstructuredList{} - deps.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Kind: "DeploymentList", - Version: "v1", - }) - err = cl.List(context.Background(), deps) - Expect(err).NotTo(HaveOccurred()) + By("creating a Deployment with the app=backend label") + depBackend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-backend", + Namespace: ns, + Labels: map[string]string{"app": "backend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - Expect(deps.Items).NotTo(BeEmpty()) - hasDep := false - for _, item := range deps.Items { - if item.GetName() == dep.Name && item.GetNamespace() == dep.Namespace { - hasDep = true - break - } - } - Expect(hasDep).To(BeTrue()) - }) + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - It("should return an empty list if there are no matching objects", func() { - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + By("listing all Deployments with label app=backend") + deps := &appsv1.DeploymentList{} + labels := map[string]string{"app": "backend"} + err = cl.List(context.Background(), deps, client.MatchingLabels(labels)) + Expect(err).NotTo(HaveOccurred()) - By("listing all Deployments in the cluster") - deps := &unstructured.UnstructuredList{} - deps.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Kind: "DeploymentList", - Version: "v1", - }) - Expect(cl.List(context.Background(), deps)).NotTo(HaveOccurred()) + By("only the Deployment with the backend label is returned") + Expect(deps.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(deps.Items))) + actual := deps.Items[0] + Expect(actual.Name).To(Equal("deployment-backend")) - By("validating no Deployments are returned") - Expect(deps.Items).To(BeEmpty()) - }) + deleteDeployment(ctx, clientset, depFrontend, ns) + deleteDeployment(ctx, clientset, depBackend, ns) + }) - It("should filter results by namespace selector", func() { - By("creating a Deployment in test-namespace-5") - tns1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-5"}} - _, err := clientset.CoreV1().Namespaces().Create(ctx, tns1, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depFrontend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: "test-namespace-5"}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + It("should filter results by namespace selector", func() { + By("creating a Deployment in test-namespace-1") + tns1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-1"}} + _, err := clientset.CoreV1().Namespaces().Create(ctx, tns1, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depFrontend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: "test-namespace-1"}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, }, - } - depFrontend, err = clientset.AppsV1().Deployments("test-namespace-5").Create(ctx, depFrontend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend, err = clientset.AppsV1().Deployments("test-namespace-1").Create(ctx, depFrontend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - By("creating a Deployment in test-namespace-6") - tns2 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-6"}} - _, err = clientset.CoreV1().Namespaces().Create(ctx, tns2, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depBackend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: "test-namespace-6"}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + By("creating a Deployment in test-namespace-2") + tns2 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-2"}} + _, err = clientset.CoreV1().Namespaces().Create(ctx, tns2, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depBackend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: "test-namespace-2"}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, }, - } - depBackend, err = clientset.AppsV1().Deployments("test-namespace-6").Create(ctx, depBackend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depBackend, err = clientset.AppsV1().Deployments("test-namespace-2").Create(ctx, depBackend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - By("listing all Deployments in test-namespace-5") - deps := &unstructured.UnstructuredList{} - deps.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Kind: "DeploymentList", - Version: "v1", - }) - err = cl.List(context.Background(), deps, client.InNamespace("test-namespace-5")) - Expect(err).NotTo(HaveOccurred()) + By("listing all Deployments in test-namespace-1") + deps := &appsv1.DeploymentList{} + err = cl.List(context.Background(), deps, client.InNamespace("test-namespace-1")) + Expect(err).NotTo(HaveOccurred()) - By("only the Deployment in test-namespace-5 is returned") - Expect(deps.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(deps.Items))) - actual := deps.Items[0] - Expect(actual.GetName()).To(Equal("deployment-frontend")) + By("only the Deployment in test-namespace-1 is returned") + Expect(deps.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(deps.Items))) + actual := deps.Items[0] + Expect(actual.Name).To(Equal("deployment-frontend")) - deleteDeployment(ctx, depFrontend, "test-namespace-5") - deleteDeployment(ctx, depBackend, "test-namespace-6") - deleteNamespace(ctx, tns1) - deleteNamespace(ctx, tns2) - }) + deleteDeployment(ctx, clientset, depFrontend, "test-namespace-1") + deleteDeployment(ctx, clientset, depBackend, "test-namespace-2") + deleteNamespace(ctx, clientset, tns1) + deleteNamespace(ctx, clientset, tns2) + }) - It("should filter results by field selector", func() { - By("creating a Deployment with name deployment-frontend") - depFrontend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: ns}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + It("should filter results by field selector", func() { + By("creating a Deployment with name deployment-frontend") + depFrontend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: ns}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, }, - } - depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - By("creating a Deployment with name deployment-backend") - depBackend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: ns}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + By("creating a Deployment with name deployment-backend") + depBackend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: ns}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, }, - } - depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - By("listing all Deployments with field metadata.name=deployment-backend") - deps := &unstructured.UnstructuredList{} - deps.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Kind: "DeploymentList", - Version: "v1", - }) - err = cl.List(context.Background(), deps, - client.MatchingFields{"metadata.name": "deployment-backend"}) - Expect(err).NotTo(HaveOccurred()) + By("listing all Deployments with field metadata.name=deployment-backend") + deps := &appsv1.DeploymentList{} + err = cl.List(context.Background(), deps, + client.MatchingFields{"metadata.name": "deployment-backend"}) + Expect(err).NotTo(HaveOccurred()) + + By("only the Deployment with the backend field is returned") + Expect(deps.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(deps.Items))) + actual := deps.Items[0] + Expect(actual.Name).To(Equal("deployment-backend")) - By("only the Deployment with the backend field is returned") - Expect(deps.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(deps.Items))) - actual := deps.Items[0] - Expect(actual.GetName()).To(Equal("deployment-backend")) + deleteDeployment(ctx, clientset, depFrontend, ns) + deleteDeployment(ctx, clientset, depBackend, ns) + }) - deleteDeployment(ctx, depFrontend, ns) - deleteDeployment(ctx, depBackend, ns) - }) + It("should filter results by namespace selector and label selector", func() { + By("creating a Deployment in test-namespace-3 with the app=frontend label") + tns3 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-3"}} + _, err := clientset.CoreV1().Namespaces().Create(ctx, tns3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depFrontend3 := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-frontend", + Namespace: "test-namespace-3", + Labels: map[string]string{"app": "frontend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend3, err = clientset.AppsV1().Deployments("test-namespace-3").Create(ctx, depFrontend3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - It("should filter results by namespace selector and label selector", func() { - By("creating a Deployment in test-namespace-7 with the app=frontend label") - tns3 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-7"}} - _, err := clientset.CoreV1().Namespaces().Create(ctx, tns3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depFrontend3 := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-frontend", - Namespace: "test-namespace-7", - Labels: map[string]string{"app": "frontend"}, + By("creating a Deployment in test-namespace-3 with the app=backend label") + depBackend3 := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-backend", + Namespace: "test-namespace-3", + Labels: map[string]string{"app": "backend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, - } - depFrontend3, err = clientset.AppsV1().Deployments("test-namespace-7").Create(ctx, depFrontend3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + }, + } + depBackend3, err = clientset.AppsV1().Deployments("test-namespace-3").Create(ctx, depBackend3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - By("creating a Deployment in test-namespace-7 with the app=backend label") - depBackend3 := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-backend", - Namespace: "test-namespace-7", - Labels: map[string]string{"app": "backend"}, + By("creating a Deployment in test-namespace-4 with the app=frontend label") + tns4 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-4"}} + _, err = clientset.CoreV1().Namespaces().Create(ctx, tns4, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depFrontend4 := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-frontend", + Namespace: "test-namespace-4", + Labels: map[string]string{"app": "frontend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, - } - depBackend3, err = clientset.AppsV1().Deployments("test-namespace-7").Create(ctx, depBackend3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + }, + } + depFrontend4, err = clientset.AppsV1().Deployments("test-namespace-4").Create(ctx, depFrontend4, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - By("creating a Deployment in test-namespace-8 with the app=frontend label") - tns4 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-8"}} - _, err = clientset.CoreV1().Namespaces().Create(ctx, tns4, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depFrontend4 := &appsv1.Deployment{ + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + + By("listing all Deployments in test-namespace-3 with label app=frontend") + deps := &appsv1.DeploymentList{} + labels := map[string]string{"app": "frontend"} + err = cl.List(context.Background(), deps, + client.InNamespace("test-namespace-3"), + client.MatchingLabels(labels), + ) + Expect(err).NotTo(HaveOccurred()) + + By("only the Deployment in test-namespace-3 with label app=frontend is returned") + Expect(deps.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(deps.Items))) + actual := deps.Items[0] + Expect(actual.Name).To(Equal("deployment-frontend")) + Expect(actual.Namespace).To(Equal("test-namespace-3")) + + deleteDeployment(ctx, clientset, depFrontend3, "test-namespace-3") + deleteDeployment(ctx, clientset, depBackend3, "test-namespace-3") + deleteDeployment(ctx, clientset, depFrontend4, "test-namespace-4") + deleteNamespace(ctx, clientset, tns3) + deleteNamespace(ctx, clientset, tns4) + }) + + It("should filter results using limit and continue options", func() { + makeDeployment := func(suffix string) *appsv1.Deployment { + return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-frontend", - Namespace: "test-namespace-8", - Labels: map[string]string{"app": "frontend"}, + Name: fmt.Sprintf("deployment-%s", suffix), }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, + MatchLabels: map[string]string{"foo": "bar"}, }, Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, }, } - depFrontend4, err = clientset.AppsV1().Deployments("test-namespace-8").Create(ctx, depFrontend4, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + } - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + By("creating 4 deployments") + dep1 := makeDeployment("1") + dep1, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep1, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep1, ns) - By("listing all Deployments in test-namespace-8 with label app=frontend") - deps := &unstructured.UnstructuredList{} - deps.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Kind: "DeploymentList", - Version: "v1", - }) - labels := map[string]string{"app": "frontend"} - err = cl.List(context.Background(), deps, - client.InNamespace("test-namespace-7"), client.MatchingLabels(labels)) - Expect(err).NotTo(HaveOccurred()) + dep2 := makeDeployment("2") + dep2, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep2, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep2, ns) - By("only the Deployment in test-namespace-7 with label app=frontend is returned") - Expect(deps.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(deps.Items))) - actual := deps.Items[0] - Expect(actual.GetName()).To(Equal("deployment-frontend")) - Expect(actual.GetNamespace()).To(Equal("test-namespace-7")) + dep3 := makeDeployment("3") + dep3, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep3, ns) - deleteDeployment(ctx, depFrontend3, "test-namespace-7") - deleteDeployment(ctx, depBackend3, "test-namespace-7") - deleteDeployment(ctx, depFrontend4, "test-namespace-8") - deleteNamespace(ctx, tns3) - deleteNamespace(ctx, tns4) - }) + dep4 := makeDeployment("4") + dep4, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep4, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep4, ns) - PIt("should fail if the object doesn't have meta", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - }) + By("listing 1 deployment when limit=1 is used") + deps := &appsv1.DeploymentList{} + err = cl.List(context.Background(), deps, + client.Limit(1), + ) + Expect(err).NotTo(HaveOccurred()) - PIt("should filter results by namespace selector", func() { + Expect(deps.Items).To(HaveLen(1)) + Expect(deps.Continue).NotTo(BeEmpty()) + Expect(deps.Items[0].Name).To(Equal(dep1.Name)) - }) + continueToken := deps.Continue + + By("listing the next deployment when previous continuation token is used and limit=1") + deps = &appsv1.DeploymentList{} + err = cl.List(context.Background(), deps, + client.Limit(1), + client.Continue(continueToken), + ) + Expect(err).NotTo(HaveOccurred()) + + Expect(deps.Items).To(HaveLen(1)) + Expect(deps.Continue).NotTo(BeEmpty()) + Expect(deps.Items[0].Name).To(Equal(dep2.Name)) + + continueToken = deps.Continue + + By("listing the 2 remaining deployments when previous continuation token is used without a limit") + deps = &appsv1.DeploymentList{} + err = cl.List(context.Background(), deps, + client.Continue(continueToken), + ) + Expect(err).NotTo(HaveOccurred()) + + Expect(deps.Items).To(HaveLen(2)) + Expect(deps.Continue).To(BeEmpty()) + Expect(deps.Items[0].Name).To(Equal(dep3.Name)) + Expect(deps.Items[1].Name).To(Equal(dep4.Name)) }) - Context("with metadata objects", func() { - It("should fetch collection of objects", func() { - By("creating an initial object") - dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + PIt("should fail if the object doesn't have meta", func() { - By("listing all objects of that type in the cluster") - gvk := schema.GroupVersionKind{ + }) + + PIt("should fail if the object cannot be mapped to a GVK", func() { + + }) + + PIt("should fail if the GVK cannot be mapped to a Resource", func() { + + }) + }) + + Context("with unstructured objects", func() { + It("should fetch collection of objects", func() { + By("create an initial object") + dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + + var logs []string + ctx := pkglog.IntoContext(context.Background(), funcr.New(func(_, msg string) { + logs = append(logs, msg) + }, funcr.Options{Verbosity: 8})) // Increase log level so we can use to determine if watchList and/or list was called. + + By("listing all objects of that type in the cluster") + gvk := schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", + } + deps := &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(gvk) + Expect(cl.List(ctx, deps)).To(Succeed()) + + By("validating that the list GVK has been preserved") + Expect(deps.GroupVersionKind()).To(Equal(gvk)) + + By("validating that the list has the expected deployment") + Expect(deps.Items).NotTo(BeEmpty()) + hasDep := false + for _, item := range deps.Items { + Expect(item.GroupVersionKind()).To(Equal(schema.GroupVersionKind{ Group: "apps", + Kind: "Deployment", Version: "v1", - Kind: "DeploymentList", - } - metaList := &metav1.PartialObjectMetadataList{} - metaList.SetGroupVersionKind(gvk) - Expect(cl.List(context.Background(), metaList)).NotTo(HaveOccurred()) - - By("validating that the list GVK has been preserved") - Expect(metaList.GroupVersionKind()).To(Equal(gvk)) - - By("validating that the list has the expected deployment") - Expect(metaList.Items).NotTo(BeEmpty()) - hasDep := false - for _, item := range metaList.Items { - Expect(item.GroupVersionKind()).To(Equal(schema.GroupVersionKind{ - Group: "apps", - Version: "v1", - Kind: "Deployment", - })) - - if item.Name == dep.Name && item.Namespace == dep.Namespace { - hasDep = true - break - } + })) + if item.GetName() == dep.Name && item.GetNamespace() == dep.Namespace { + hasDep = true + break } - Expect(hasDep).To(BeTrue()) - }) + } + Expect(hasDep).To(BeTrue()) + + switch { + case watchListEnabled && watchListClientEnabled: + // Expect successful watchList + // We expect that no response bodies are logged + Expect(logs).ToNot(ContainElement(MatchRegexp("Response Body.*DeploymentList"))) + case !watchListEnabled && watchListClientEnabled: + // Expect failed watchList + successful fallback to list + Expect(logs).To(ContainElements( + "\"msg\"=\"Warning: The watchlist request for deployments ended with an error, " + + "falling back to the standard LIST semantics\" " + + "\"error\"=\"ListOptions.meta.k8s.io \\\"\\\" is invalid: sendInitialEvents: " + + "Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled\"")) + // Fallback list logs response body + Expect(logs).To(ContainElement(MatchRegexp("Response Body.*DeploymentList"))) + case watchListEnabled && !watchListClientEnabled: + // Expect successful list + // List logs response body + Expect(logs).To(ContainElement(MatchRegexp("Response Body.*DeploymentList"))) + case !watchListEnabled && !watchListClientEnabled: + // Expect successful list + // List logs response body + Expect(logs).To(ContainElement(MatchRegexp("Response Body.*DeploymentList"))) + } + }) - It("should return an empty list if there are no matching objects", func() { - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + It("should fetch collection of objects, even if scheme is empty", func() { + By("create an initial object") + _, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) + Expect(err).NotTo(HaveOccurred()) + + By("listing all objects of that type in the cluster") + deps := &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", + }) + err = cl.List(context.Background(), deps) + Expect(err).NotTo(HaveOccurred()) - By("listing all Deployments in the cluster") - metaList := &metav1.PartialObjectMetadataList{} - metaList.SetGroupVersionKind(schema.GroupVersionKind{ + Expect(deps.Items).NotTo(BeEmpty()) + hasDep := false + for _, item := range deps.Items { + Expect(item.GroupVersionKind()).To(Equal(schema.GroupVersionKind{ Group: "apps", + Kind: "Deployment", Version: "v1", - Kind: "DeploymentList", - }) - Expect(cl.List(context.Background(), metaList)).NotTo(HaveOccurred()) + })) + if item.GetName() == dep.Name && item.GetNamespace() == dep.Namespace { + hasDep = true + break + } + } + Expect(hasDep).To(BeTrue()) + }) - By("validating no Deployments are returned") - Expect(metaList.Items).To(BeEmpty()) + It("should return an empty list if there are no matching objects", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + + By("listing all Deployments in the cluster") + deps := &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", }) + Expect(cl.List(context.Background(), deps)).NotTo(HaveOccurred()) - // TODO(seans): get label selector test working - It("should filter results by label selector", func() { - By("creating a Deployment with the app=frontend label") - depFrontend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-frontend", - Namespace: ns, - Labels: map[string]string{"app": "frontend"}, + By("validating no Deployments are returned") + Expect(deps.Items).To(BeEmpty()) + }) + + // TODO(seans): get label selector test working + It("should filter results by label selector", func() { + By("creating a Deployment with the app=frontend label") + depFrontend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-frontend", + Namespace: ns, + Labels: map[string]string{"app": "frontend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, - } - depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + }, + } + depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - By("creating a Deployment with the app=backend label") - depBackend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-backend", - Namespace: ns, - Labels: map[string]string{"app": "backend"}, + By("creating a Deployment with the app=backend label") + depBackend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-backend", + Namespace: ns, + Labels: map[string]string{"app": "backend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, - } - depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + }, + } + depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - By("listing all Deployments with label app=backend") - metaList := &metav1.PartialObjectMetadataList{} - metaList.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Version: "v1", - Kind: "DeploymentList", - }) - labels := map[string]string{"app": "backend"} - err = cl.List(context.Background(), metaList, client.MatchingLabels(labels)) - Expect(err).NotTo(HaveOccurred()) + By("listing all Deployments with label app=backend") + deps := &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", + }) + labels := map[string]string{"app": "backend"} + err = cl.List(context.Background(), deps, client.MatchingLabels(labels)) + Expect(err).NotTo(HaveOccurred()) - By("only the Deployment with the backend label is returned") - Expect(metaList.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(metaList.Items))) - actual := metaList.Items[0] - Expect(actual.Name).To(Equal("deployment-backend")) + By("only the Deployment with the backend label is returned") + Expect(deps.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(deps.Items))) + actual := deps.Items[0] + Expect(actual.GetName()).To(Equal("deployment-backend")) - deleteDeployment(ctx, depFrontend, ns) - deleteDeployment(ctx, depBackend, ns) - }) + deleteDeployment(ctx, clientset, depFrontend, ns) + deleteDeployment(ctx, clientset, depBackend, ns) + }) - It("should filter results by namespace selector", func() { - By("creating a Deployment in test-namespace-1") - tns1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-1"}} - _, err := clientset.CoreV1().Namespaces().Create(ctx, tns1, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depFrontend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: "test-namespace-1"}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + It("should filter results by namespace selector", func() { + By("creating a Deployment in test-namespace-5") + tns1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-5"}} + _, err := clientset.CoreV1().Namespaces().Create(ctx, tns1, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depFrontend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: "test-namespace-5"}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, }, - } - depFrontend, err = clientset.AppsV1().Deployments("test-namespace-1").Create(ctx, depFrontend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend, err = clientset.AppsV1().Deployments("test-namespace-5").Create(ctx, depFrontend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - By("creating a Deployment in test-namespace-2") - tns2 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-2"}} - _, err = clientset.CoreV1().Namespaces().Create(ctx, tns2, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depBackend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: "test-namespace-2"}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + By("creating a Deployment in test-namespace-6") + tns2 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-6"}} + _, err = clientset.CoreV1().Namespaces().Create(ctx, tns2, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depBackend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: "test-namespace-6"}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, }, - } - depBackend, err = clientset.AppsV1().Deployments("test-namespace-2").Create(ctx, depBackend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depBackend, err = clientset.AppsV1().Deployments("test-namespace-6").Create(ctx, depBackend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - By("listing all Deployments in test-namespace-1") - metaList := &metav1.PartialObjectMetadataList{} - metaList.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Version: "v1", - Kind: "DeploymentList", - }) - err = cl.List(context.Background(), metaList, client.InNamespace("test-namespace-1")) - Expect(err).NotTo(HaveOccurred()) + By("listing all Deployments in test-namespace-5") + deps := &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", + }) + err = cl.List(context.Background(), deps, client.InNamespace("test-namespace-5")) + Expect(err).NotTo(HaveOccurred()) - By("only the Deployment in test-namespace-1 is returned") - Expect(metaList.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(metaList.Items))) - actual := metaList.Items[0] - Expect(actual.Name).To(Equal("deployment-frontend")) + By("only the Deployment in test-namespace-5 is returned") + Expect(deps.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(deps.Items))) + actual := deps.Items[0] + Expect(actual.GetName()).To(Equal("deployment-frontend")) - deleteDeployment(ctx, depFrontend, "test-namespace-1") - deleteDeployment(ctx, depBackend, "test-namespace-2") - deleteNamespace(ctx, tns1) - deleteNamespace(ctx, tns2) - }) + deleteDeployment(ctx, clientset, depFrontend, "test-namespace-5") + deleteDeployment(ctx, clientset, depBackend, "test-namespace-6") + deleteNamespace(ctx, clientset, tns1) + deleteNamespace(ctx, clientset, tns2) + }) - It("should filter results by field selector", func() { - By("creating a Deployment with name deployment-frontend") - depFrontend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: ns}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + It("should filter results by field selector", func() { + By("creating a Deployment with name deployment-frontend") + depFrontend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: ns}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, }, - } - depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - By("creating a Deployment with name deployment-backend") - depBackend := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: ns}, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("creating a Deployment with name deployment-backend") + depBackend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: ns}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, }, - } - depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - By("listing all Deployments with field metadata.name=deployment-backend") - metaList := &metav1.PartialObjectMetadataList{} - metaList.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Version: "v1", - Kind: "DeploymentList", - }) - err = cl.List(context.Background(), metaList, - client.MatchingFields{"metadata.name": "deployment-backend"}) - Expect(err).NotTo(HaveOccurred()) + By("listing all Deployments with field metadata.name=deployment-backend") + deps := &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", + }) + err = cl.List(context.Background(), deps, + client.MatchingFields{"metadata.name": "deployment-backend"}) + Expect(err).NotTo(HaveOccurred()) - By("only the Deployment with the backend field is returned") - Expect(metaList.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(metaList.Items))) - actual := metaList.Items[0] - Expect(actual.Name).To(Equal("deployment-backend")) + By("only the Deployment with the backend field is returned") + Expect(deps.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(deps.Items))) + actual := deps.Items[0] + Expect(actual.GetName()).To(Equal("deployment-backend")) - deleteDeployment(ctx, depFrontend, ns) - deleteDeployment(ctx, depBackend, ns) - }) + deleteDeployment(ctx, clientset, depFrontend, ns) + deleteDeployment(ctx, clientset, depBackend, ns) + }) - It("should filter results by namespace selector and label selector", func() { - By("creating a Deployment in test-namespace-3 with the app=frontend label") - tns3 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-3"}} - _, err := clientset.CoreV1().Namespaces().Create(ctx, tns3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depFrontend3 := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-frontend", - Namespace: "test-namespace-3", - Labels: map[string]string{"app": "frontend"}, + It("should filter results by namespace selector and label selector", func() { + By("creating a Deployment in test-namespace-7 with the app=frontend label") + tns3 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-7"}} + _, err := clientset.CoreV1().Namespaces().Create(ctx, tns3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depFrontend3 := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-frontend", + Namespace: "test-namespace-7", + Labels: map[string]string{"app": "frontend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, - } - depFrontend3, err = clientset.AppsV1().Deployments("test-namespace-3").Create(ctx, depFrontend3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + }, + } + depFrontend3, err = clientset.AppsV1().Deployments("test-namespace-7").Create(ctx, depFrontend3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - By("creating a Deployment in test-namespace-3 with the app=backend label") - depBackend3 := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-backend", - Namespace: "test-namespace-3", - Labels: map[string]string{"app": "backend"}, + By("creating a Deployment in test-namespace-7 with the app=backend label") + depBackend3 := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-backend", + Namespace: "test-namespace-7", + Labels: map[string]string{"app": "backend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "backend"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, - } - depBackend3, err = clientset.AppsV1().Deployments("test-namespace-3").Create(ctx, depBackend3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + }, + } + depBackend3, err = clientset.AppsV1().Deployments("test-namespace-7").Create(ctx, depBackend3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - By("creating a Deployment in test-namespace-4 with the app=frontend label") - tns4 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-4"}} - _, err = clientset.CoreV1().Namespaces().Create(ctx, tns4, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - depFrontend4 := &appsv1.Deployment{ + By("creating a Deployment in test-namespace-8 with the app=frontend label") + tns4 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-8"}} + _, err = clientset.CoreV1().Namespaces().Create(ctx, tns4, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depFrontend4 := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-frontend", + Namespace: "test-namespace-8", + Labels: map[string]string{"app": "frontend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend4, err = clientset.AppsV1().Deployments("test-namespace-8").Create(ctx, depFrontend4, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + + By("listing all Deployments in test-namespace-8 with label app=frontend") + deps := &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", + }) + labels := map[string]string{"app": "frontend"} + err = cl.List(context.Background(), deps, + client.InNamespace("test-namespace-7"), client.MatchingLabels(labels)) + Expect(err).NotTo(HaveOccurred()) + + By("only the Deployment in test-namespace-7 with label app=frontend is returned") + Expect(deps.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(deps.Items))) + actual := deps.Items[0] + Expect(actual.GetName()).To(Equal("deployment-frontend")) + Expect(actual.GetNamespace()).To(Equal("test-namespace-7")) + + deleteDeployment(ctx, clientset, depFrontend3, "test-namespace-7") + deleteDeployment(ctx, clientset, depBackend3, "test-namespace-7") + deleteDeployment(ctx, clientset, depFrontend4, "test-namespace-8") + deleteNamespace(ctx, clientset, tns3) + deleteNamespace(ctx, clientset, tns4) + }) + + It("should filter results using limit and continue options", func() { + makeDeployment := func(suffix string) *appsv1.Deployment { + return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: "deployment-frontend", - Namespace: "test-namespace-4", - Labels: map[string]string{"app": "frontend"}, + Name: fmt.Sprintf("deployment-%s", suffix), }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "frontend"}, + MatchLabels: map[string]string{"foo": "bar"}, }, Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, }, } - depFrontend4, err = clientset.AppsV1().Deployments("test-namespace-4").Create(ctx, depFrontend4, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + } - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + By("creating 4 deployments") + dep1 := makeDeployment("1") + dep1, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep1, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep1, ns) - By("listing all Deployments in test-namespace-3 with label app=frontend") - metaList := &metav1.PartialObjectMetadataList{} - metaList.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Version: "v1", - Kind: "DeploymentList", - }) - labels := map[string]string{"app": "frontend"} - err = cl.List(context.Background(), metaList, - client.InNamespace("test-namespace-3"), - client.MatchingLabels(labels), - ) - Expect(err).NotTo(HaveOccurred()) + dep2 := makeDeployment("2") + dep2, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep2, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep2, ns) - By("only the Deployment in test-namespace-3 with label app=frontend is returned") - Expect(metaList.Items).NotTo(BeEmpty()) - Expect(1).To(Equal(len(metaList.Items))) - actual := metaList.Items[0] - Expect(actual.Name).To(Equal("deployment-frontend")) - Expect(actual.Namespace).To(Equal("test-namespace-3")) + dep3 := makeDeployment("3") + dep3, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep3, ns) - deleteDeployment(ctx, depFrontend3, "test-namespace-3") - deleteDeployment(ctx, depBackend3, "test-namespace-3") - deleteDeployment(ctx, depFrontend4, "test-namespace-4") - deleteNamespace(ctx, tns3) - deleteNamespace(ctx, tns4) - }) + dep4 := makeDeployment("4") + dep4, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep4, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep4, ns) - It("should filter results using limit and continue options", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - makeDeployment := func(suffix string) *appsv1.Deployment { - return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("deployment-%s", suffix), - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"foo": "bar"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, - }, - } - } + By("listing 1 deployment when limit=1 is used") + deps := &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", + }) + err = cl.List(context.Background(), deps, + client.Limit(1), + ) + Expect(err).NotTo(HaveOccurred()) - By("creating 4 deployments") - dep1 := makeDeployment("1") - dep1, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep1, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - defer deleteDeployment(ctx, dep1, ns) + Expect(deps.Items).To(HaveLen(1)) + Expect(deps.GetContinue()).NotTo(BeEmpty()) + Expect(deps.Items[0].GetName()).To(Equal(dep1.Name)) - dep2 := makeDeployment("2") - dep2, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep2, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - defer deleteDeployment(ctx, dep2, ns) + continueToken := deps.GetContinue() - dep3 := makeDeployment("3") - dep3, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep3, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - defer deleteDeployment(ctx, dep3, ns) + By("listing the next deployment when previous continuation token is used and limit=1") + deps = &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", + }) + err = cl.List(context.Background(), deps, + client.Limit(1), + client.Continue(continueToken), + ) + Expect(err).NotTo(HaveOccurred()) - dep4 := makeDeployment("4") - dep4, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep4, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - defer deleteDeployment(ctx, dep4, ns) + Expect(deps.Items).To(HaveLen(1)) + Expect(deps.GetContinue()).NotTo(BeEmpty()) + Expect(deps.Items[0].GetName()).To(Equal(dep2.Name)) - cl, err := client.New(cfg, client.Options{}) - Expect(err).NotTo(HaveOccurred()) + continueToken = deps.GetContinue() - By("listing 1 deployment when limit=1 is used") - metaList := &metav1.PartialObjectMetadataList{} - metaList.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Version: "v1", - Kind: "DeploymentList", - }) - err = cl.List(context.Background(), metaList, - client.Limit(1), - ) - Expect(err).NotTo(HaveOccurred()) + By("listing the 2 remaining deployments when previous continuation token is used without a limit") + deps = &unstructured.UnstructuredList{} + deps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Kind: "DeploymentList", + Version: "v1", + }) + err = cl.List(context.Background(), deps, + client.Continue(continueToken), + ) + Expect(err).NotTo(HaveOccurred()) - Expect(metaList.Items).To(HaveLen(1)) - Expect(metaList.Continue).NotTo(BeEmpty()) - Expect(metaList.Items[0].Name).To(Equal(dep1.Name)) + Expect(deps.Items).To(HaveLen(2)) + Expect(deps.GetContinue()).To(BeEmpty()) + Expect(deps.Items[0].GetName()).To(Equal(dep3.Name)) + Expect(deps.Items[1].GetName()).To(Equal(dep4.Name)) + }) - continueToken := metaList.Continue + PIt("should fail if the object doesn't have meta", func() { - By("listing the next deployment when previous continuation token is used and limit=1") - metaList = &metav1.PartialObjectMetadataList{} - metaList.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Version: "v1", - Kind: "DeploymentList", - }) - err = cl.List(context.Background(), metaList, - client.Limit(1), - client.Continue(continueToken), - ) - Expect(err).NotTo(HaveOccurred()) + }) + }) - Expect(metaList.Items).To(HaveLen(1)) - Expect(metaList.Continue).NotTo(BeEmpty()) - Expect(metaList.Items[0].Name).To(Equal(dep2.Name)) + Context("with metadata objects", func() { + It("should fetch collection of objects", func() { + By("creating an initial object") + dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - continueToken = metaList.Continue + var logs []string + ctx := pkglog.IntoContext(context.Background(), funcr.New(func(_, msg string) { + logs = append(logs, msg) + }, funcr.Options{Verbosity: 8})) // Increase log level so we can use to determine if watchList and/or list was called. - By("listing the 2 remaining deployments when previous continuation token is used without a limit") - metaList = &metav1.PartialObjectMetadataList{} - metaList.SetGroupVersionKind(schema.GroupVersionKind{ + By("listing all objects of that type in the cluster") + gvk := schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DeploymentList", + } + deps := &metav1.PartialObjectMetadataList{} + deps.SetGroupVersionKind(gvk) + Expect(cl.List(ctx, deps)).To(Succeed()) + + By("validating that the list GVK has been preserved") + Expect(deps.GroupVersionKind()).To(Equal(gvk)) + + By("validating that the list has the expected deployment") + Expect(deps.Items).NotTo(BeEmpty()) + hasDep := false + for _, item := range deps.Items { + Expect(item.GroupVersionKind()).To(Equal(schema.GroupVersionKind{ Group: "apps", Version: "v1", - Kind: "DeploymentList", - }) - err = cl.List(context.Background(), metaList, - client.Continue(continueToken), - ) - Expect(err).NotTo(HaveOccurred()) - - Expect(metaList.Items).To(HaveLen(2)) - Expect(metaList.Continue).To(BeEmpty()) - Expect(metaList.Items[0].Name).To(Equal(dep3.Name)) - Expect(metaList.Items[1].Name).To(Equal(dep4.Name)) - }) - - PIt("should fail if the object doesn't have meta", func() { - - }) - - PIt("should fail if the object cannot be mapped to a GVK", func() { + Kind: "Deployment", + })) - }) + if item.Name == dep.Name && item.Namespace == dep.Namespace { + hasDep = true + break + } + } + Expect(hasDep).To(BeTrue()) + + switch { + case watchListEnabled && watchListClientEnabled: + // Expect successful watchList + // We expect that no response bodies are logged + Expect(logs).ToNot(ContainElement(MatchRegexp("Response Body.*MetadataLis"))) + case !watchListEnabled && watchListClientEnabled: + // Expect failed watchList + successful fallback to list + Expect(logs).To(ContainElements("" + + "\"msg\"=\"The watchlist request ended with an error, " + + "falling back to the standard LIST semantics\" " + + "\"error\"=\"ListOptions.meta.k8s.io \\\"\\\" is invalid: sendInitialEvents: " + + "Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled\" " + + "\"resource\"=\"apps/v1, Resource=deployments\"")) + // Fallback list logs response body (body is logged in a not ideal way, so can only match on MetadataLis) + Expect(logs).To(ContainElement(MatchRegexp("Response Body.*MetadataLis"))) + case watchListEnabled && !watchListClientEnabled: + // Expect successful list + // List logs response body (body is logged in a not ideal way, so can only match on MetadataLis) + Expect(logs).To(ContainElement(MatchRegexp("Response Body.*MetadataLis"))) + case !watchListEnabled && !watchListClientEnabled: + // Expect successful list + // List logs response body (body is logged in a not ideal way, so can only match on MetadataLis) + Expect(logs).To(ContainElement(MatchRegexp("Response Body.*MetadataLis"))) + } + }) - PIt("should fail if the GVK cannot be mapped to a Resource", func() { + It("should return an empty list if there are no matching objects", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + By("listing all Deployments in the cluster") + metaList := &metav1.PartialObjectMetadataList{} + metaList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DeploymentList", }) - }) - }) + Expect(cl.List(context.Background(), metaList)).NotTo(HaveOccurred()) - Describe("CreateOptions", func() { - It("should allow setting DryRun to 'all'", func() { - co := &client.CreateOptions{} - client.DryRunAll.ApplyToCreate(co) - all := []string{metav1.DryRunAll} - Expect(co.AsCreateOptions().DryRun).To(Equal(all)) + By("validating no Deployments are returned") + Expect(metaList.Items).To(BeEmpty()) }) - It("should allow setting the field manager", func() { - po := &client.CreateOptions{} - client.FieldOwner("some-owner").ApplyToCreate(po) - Expect(po.AsCreateOptions().FieldManager).To(Equal("some-owner")) - }) + // TODO(seans): get label selector test working + It("should filter results by label selector", func() { + By("creating a Deployment with the app=frontend label") + depFrontend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-frontend", + Namespace: ns, + Labels: map[string]string{"app": "frontend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - It("should produce empty metav1.CreateOptions if nil", func() { - var co *client.CreateOptions - Expect(co.AsCreateOptions()).To(Equal(&metav1.CreateOptions{})) - co = &client.CreateOptions{} - Expect(co.AsCreateOptions()).To(Equal(&metav1.CreateOptions{})) - }) - }) + By("creating a Deployment with the app=backend label") + depBackend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-backend", + Namespace: ns, + Labels: map[string]string{"app": "backend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - Describe("DeleteOptions", func() { - It("should allow setting GracePeriodSeconds", func() { - do := &client.DeleteOptions{} - client.GracePeriodSeconds(1).ApplyToDelete(do) - gp := int64(1) - Expect(do.AsDeleteOptions().GracePeriodSeconds).To(Equal(&gp)) - }) + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - It("should allow setting Precondition", func() { - do := &client.DeleteOptions{} - pc := metav1.NewUIDPreconditions("uid") - client.Preconditions(*pc).ApplyToDelete(do) - Expect(do.AsDeleteOptions().Preconditions).To(Equal(pc)) - Expect(do.Preconditions).To(Equal(pc)) - }) + By("listing all Deployments with label app=backend") + metaList := &metav1.PartialObjectMetadataList{} + metaList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DeploymentList", + }) + labels := map[string]string{"app": "backend"} + err = cl.List(context.Background(), metaList, client.MatchingLabels(labels)) + Expect(err).NotTo(HaveOccurred()) - It("should allow setting PropagationPolicy", func() { - do := &client.DeleteOptions{} - client.PropagationPolicy(metav1.DeletePropagationForeground).ApplyToDelete(do) - dp := metav1.DeletePropagationForeground - Expect(do.AsDeleteOptions().PropagationPolicy).To(Equal(&dp)) - }) + By("only the Deployment with the backend label is returned") + Expect(metaList.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(metaList.Items))) + actual := metaList.Items[0] + Expect(actual.Name).To(Equal("deployment-backend")) - It("should allow setting DryRun", func() { - do := &client.DeleteOptions{} - client.DryRunAll.ApplyToDelete(do) - all := []string{metav1.DryRunAll} - Expect(do.AsDeleteOptions().DryRun).To(Equal(all)) + deleteDeployment(ctx, clientset, depFrontend, ns) + deleteDeployment(ctx, clientset, depBackend, ns) }) - It("should produce empty metav1.DeleteOptions if nil", func() { - var do *client.DeleteOptions - Expect(do.AsDeleteOptions()).To(Equal(&metav1.DeleteOptions{})) - do = &client.DeleteOptions{} - Expect(do.AsDeleteOptions()).To(Equal(&metav1.DeleteOptions{})) - }) + It("should filter results by namespace selector", func() { + By("creating a Deployment in test-namespace-1") + tns1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-1"}} + _, err := clientset.CoreV1().Namespaces().Create(ctx, tns1, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depFrontend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: "test-namespace-1"}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend, err = clientset.AppsV1().Deployments("test-namespace-1").Create(ctx, depFrontend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - It("should merge multiple options together", func() { - gp := int64(1) - pc := metav1.NewUIDPreconditions("uid") - dp := metav1.DeletePropagationForeground - do := &client.DeleteOptions{} - do.ApplyOptions([]client.DeleteOption{ - client.GracePeriodSeconds(gp), - client.Preconditions(*pc), - client.PropagationPolicy(dp), - }) - Expect(do.GracePeriodSeconds).To(Equal(&gp)) - Expect(do.Preconditions).To(Equal(pc)) - Expect(do.PropagationPolicy).To(Equal(&dp)) - }) - }) + By("creating a Deployment in test-namespace-2") + tns2 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-2"}} + _, err = clientset.CoreV1().Namespaces().Create(ctx, tns2, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depBackend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: "test-namespace-2"}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depBackend, err = clientset.AppsV1().Deployments("test-namespace-2").Create(ctx, depBackend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - Describe("DeleteCollectionOptions", func() { - It("should be convertable to list options", func() { - gp := int64(1) - do := &client.DeleteAllOfOptions{} - do.ApplyOptions([]client.DeleteAllOfOption{ - client.GracePeriodSeconds(gp), - client.MatchingLabels{"foo": "bar"}, + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + + By("listing all Deployments in test-namespace-1") + metaList := &metav1.PartialObjectMetadataList{} + metaList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DeploymentList", }) + err = cl.List(context.Background(), metaList, client.InNamespace("test-namespace-1")) + Expect(err).NotTo(HaveOccurred()) - listOpts := do.AsListOptions() - Expect(listOpts).NotTo(BeNil()) - Expect(listOpts.LabelSelector).To(Equal("foo=bar")) + By("only the Deployment in test-namespace-1 is returned") + Expect(metaList.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(metaList.Items))) + actual := metaList.Items[0] + Expect(actual.Name).To(Equal("deployment-frontend")) + + deleteDeployment(ctx, clientset, depFrontend, "test-namespace-1") + deleteDeployment(ctx, clientset, depBackend, "test-namespace-2") + deleteNamespace(ctx, clientset, tns1) + deleteNamespace(ctx, clientset, tns2) }) - It("should be convertable to delete options", func() { - gp := int64(1) - do := &client.DeleteAllOfOptions{} - do.ApplyOptions([]client.DeleteAllOfOption{ - client.GracePeriodSeconds(gp), - client.MatchingLabels{"foo": "bar"}, - }) + It("should filter results by field selector", func() { + By("creating a Deployment with name deployment-frontend") + depFrontend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-frontend", Namespace: ns}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend, err := clientset.AppsV1().Deployments(ns).Create(ctx, depFrontend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - deleteOpts := do.AsDeleteOptions() - Expect(deleteOpts).NotTo(BeNil()) - Expect(deleteOpts.GracePeriodSeconds).To(Equal(&gp)) - }) - }) + By("creating a Deployment with name deployment-backend") + depBackend := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "deployment-backend", Namespace: ns}, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - Describe("GetOptions", func() { - It("should be convertable to metav1.GetOptions", func() { - o := (&client.GetOptions{}).ApplyOptions([]client.GetOption{ - &client.GetOptions{Raw: &metav1.GetOptions{ResourceVersion: "RV0"}}, + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + + By("listing all Deployments with field metadata.name=deployment-backend") + metaList := &metav1.PartialObjectMetadataList{} + metaList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DeploymentList", }) - mo := o.AsGetOptions() - Expect(mo).NotTo(BeNil()) - Expect(mo.ResourceVersion).To(Equal("RV0")) - }) + err = cl.List(context.Background(), metaList, + client.MatchingFields{"metadata.name": "deployment-backend"}) + Expect(err).NotTo(HaveOccurred()) - It("should produce empty metav1.GetOptions if nil", func() { - var o *client.GetOptions - Expect(o.AsGetOptions()).To(Equal(&metav1.GetOptions{})) - o = &client.GetOptions{} - Expect(o.AsGetOptions()).To(Equal(&metav1.GetOptions{})) - }) - }) + By("only the Deployment with the backend field is returned") + Expect(metaList.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(metaList.Items))) + actual := metaList.Items[0] + Expect(actual.Name).To(Equal("deployment-backend")) - Describe("ListOptions", func() { - It("should be convertable to metav1.ListOptions", func() { - lo := (&client.ListOptions{}).ApplyOptions([]client.ListOption{ - client.MatchingFields{"field1": "bar"}, - client.InNamespace("test-namespace"), - client.MatchingLabels{"foo": "bar"}, - client.Limit(1), - client.Continue("foo"), - }) - mlo := lo.AsListOptions() - Expect(mlo).NotTo(BeNil()) - Expect(mlo.LabelSelector).To(Equal("foo=bar")) - Expect(mlo.FieldSelector).To(Equal("field1=bar")) - Expect(mlo.Limit).To(Equal(int64(1))) - Expect(mlo.Continue).To(Equal("foo")) + deleteDeployment(ctx, clientset, depFrontend, ns) + deleteDeployment(ctx, clientset, depBackend, ns) }) - It("should be populated by MatchingLabels", func() { - lo := &client.ListOptions{} - client.MatchingLabels{"foo": "bar"}.ApplyToList(lo) - Expect(lo).NotTo(BeNil()) - Expect(lo.LabelSelector.String()).To(Equal("foo=bar")) - }) + It("should filter results by namespace selector and label selector", func() { + By("creating a Deployment in test-namespace-3 with the app=frontend label") + tns3 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-3"}} + _, err := clientset.CoreV1().Namespaces().Create(ctx, tns3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depFrontend3 := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-frontend", + Namespace: "test-namespace-3", + Labels: map[string]string{"app": "frontend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend3, err = clientset.AppsV1().Deployments("test-namespace-3").Create(ctx, depFrontend3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - It("should be populated by MatchingField", func() { - lo := &client.ListOptions{} - client.MatchingFields{"field1": "bar"}.ApplyToList(lo) - Expect(lo).NotTo(BeNil()) - Expect(lo.FieldSelector.String()).To(Equal("field1=bar")) - }) + By("creating a Deployment in test-namespace-3 with the app=backend label") + depBackend3 := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-backend", + Namespace: "test-namespace-3", + Labels: map[string]string{"app": "backend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "backend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "backend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depBackend3, err = clientset.AppsV1().Deployments("test-namespace-3").Create(ctx, depBackend3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - It("should be populated by InNamespace", func() { - lo := &client.ListOptions{} - client.InNamespace("test").ApplyToList(lo) - Expect(lo).NotTo(BeNil()) - Expect(lo.Namespace).To(Equal("test")) - }) + By("creating a Deployment in test-namespace-4 with the app=frontend label") + tns4 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace-4"}} + _, err = clientset.CoreV1().Namespaces().Create(ctx, tns4, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + depFrontend4 := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-frontend", + Namespace: "test-namespace-4", + Labels: map[string]string{"app": "frontend"}, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "frontend"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "frontend"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } + depFrontend4, err = clientset.AppsV1().Deployments("test-namespace-4").Create(ctx, depFrontend4, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - It("should produce empty metav1.ListOptions if nil", func() { - var do *client.ListOptions - Expect(do.AsListOptions()).To(Equal(&metav1.ListOptions{})) - do = &client.ListOptions{} - Expect(do.AsListOptions()).To(Equal(&metav1.ListOptions{})) - }) + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) - It("should be populated by Limit", func() { - lo := &client.ListOptions{} - client.Limit(1).ApplyToList(lo) - Expect(lo).NotTo(BeNil()) - Expect(lo.Limit).To(Equal(int64(1))) + By("listing all Deployments in test-namespace-3 with label app=frontend") + metaList := &metav1.PartialObjectMetadataList{} + metaList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DeploymentList", + }) + labels := map[string]string{"app": "frontend"} + err = cl.List(context.Background(), metaList, + client.InNamespace("test-namespace-3"), + client.MatchingLabels(labels), + ) + Expect(err).NotTo(HaveOccurred()) + + By("only the Deployment in test-namespace-3 with label app=frontend is returned") + Expect(metaList.Items).NotTo(BeEmpty()) + Expect(1).To(Equal(len(metaList.Items))) + actual := metaList.Items[0] + Expect(actual.Name).To(Equal("deployment-frontend")) + Expect(actual.Namespace).To(Equal("test-namespace-3")) + + deleteDeployment(ctx, clientset, depFrontend3, "test-namespace-3") + deleteDeployment(ctx, clientset, depBackend3, "test-namespace-3") + deleteDeployment(ctx, clientset, depFrontend4, "test-namespace-4") + deleteNamespace(ctx, clientset, tns3) + deleteNamespace(ctx, clientset, tns4) }) - It("should ignore Limit when converted to metav1.ListOptions and watch is true", func() { - lo := &client.ListOptions{ - Raw: &metav1.ListOptions{Watch: true}, + It("should filter results using limit and continue options", func() { + makeDeployment := func(suffix string) *appsv1.Deployment { + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("deployment-%s", suffix), + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, + }, + } } - lo.ApplyOptions([]client.ListOption{ - client.Limit(1), - }) - mlo := lo.AsListOptions() - Expect(mlo).NotTo(BeNil()) - Expect(mlo.Limit).To(BeZero()) - }) - It("should be populated by Continue", func() { - lo := &client.ListOptions{} - client.Continue("foo").ApplyToList(lo) - Expect(lo).NotTo(BeNil()) - Expect(lo.Continue).To(Equal("foo")) - }) + By("creating 4 deployments") + dep1 := makeDeployment("1") + dep1, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep1, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep1, ns) - It("should ignore Continue token when converted to metav1.ListOptions and watch is true", func() { - lo := &client.ListOptions{ - Raw: &metav1.ListOptions{Watch: true}, - } - lo.ApplyOptions([]client.ListOption{ - client.Continue("foo"), + dep2 := makeDeployment("2") + dep2, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep2, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep2, ns) + + dep3 := makeDeployment("3") + dep3, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep3, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep3, ns) + + dep4 := makeDeployment("4") + dep4, err = clientset.AppsV1().Deployments(ns).Create(ctx, dep4, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer deleteDeployment(ctx, clientset, dep4, ns) + + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + + By("listing 1 deployment when limit=1 is used") + metaList := &metav1.PartialObjectMetadataList{} + metaList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DeploymentList", }) - mlo := lo.AsListOptions() - Expect(mlo).NotTo(BeNil()) - Expect(mlo.Continue).To(BeEmpty()) - }) + err = cl.List(context.Background(), metaList, + client.Limit(1), + ) + Expect(err).NotTo(HaveOccurred()) - It("should ignore both Limit and Continue token when converted to metav1.ListOptions and watch is true", func() { - lo := &client.ListOptions{ - Raw: &metav1.ListOptions{Watch: true}, - } - lo.ApplyOptions([]client.ListOption{ + Expect(metaList.Items).To(HaveLen(1)) + Expect(metaList.Continue).NotTo(BeEmpty()) + Expect(metaList.Items[0].Name).To(Equal(dep1.Name)) + + continueToken := metaList.Continue + + By("listing the next deployment when previous continuation token is used and limit=1") + metaList = &metav1.PartialObjectMetadataList{} + metaList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DeploymentList", + }) + err = cl.List(context.Background(), metaList, client.Limit(1), - client.Continue("foo"), + client.Continue(continueToken), + ) + Expect(err).NotTo(HaveOccurred()) + + Expect(metaList.Items).To(HaveLen(1)) + Expect(metaList.Continue).NotTo(BeEmpty()) + Expect(metaList.Items[0].Name).To(Equal(dep2.Name)) + + continueToken = metaList.Continue + + By("listing the 2 remaining deployments when previous continuation token is used without a limit") + metaList = &metav1.PartialObjectMetadataList{} + metaList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DeploymentList", }) - mlo := lo.AsListOptions() - Expect(mlo).NotTo(BeNil()) - Expect(mlo.Limit).To(BeZero()) - Expect(mlo.Continue).To(BeEmpty()) - }) - }) + err = cl.List(context.Background(), metaList, + client.Continue(continueToken), + ) + Expect(err).NotTo(HaveOccurred()) - Describe("UpdateOptions", func() { - It("should allow setting DryRun to 'all'", func() { - uo := &client.UpdateOptions{} - client.DryRunAll.ApplyToUpdate(uo) - all := []string{metav1.DryRunAll} - Expect(uo.AsUpdateOptions().DryRun).To(Equal(all)) + Expect(metaList.Items).To(HaveLen(2)) + Expect(metaList.Continue).To(BeEmpty()) + Expect(metaList.Items[0].Name).To(Equal(dep3.Name)) + Expect(metaList.Items[1].Name).To(Equal(dep4.Name)) }) - It("should allow setting the field manager", func() { - po := &client.UpdateOptions{} - client.FieldOwner("some-owner").ApplyToUpdate(po) - Expect(po.AsUpdateOptions().FieldManager).To(Equal("some-owner")) - }) + PIt("should fail if the object doesn't have meta", func() { - It("should produce empty metav1.UpdateOptions if nil", func() { - var co *client.UpdateOptions - Expect(co.AsUpdateOptions()).To(Equal(&metav1.UpdateOptions{})) - co = &client.UpdateOptions{} - Expect(co.AsUpdateOptions()).To(Equal(&metav1.UpdateOptions{})) }) - }) - Describe("PatchOptions", func() { - It("should allow setting DryRun to 'all'", func() { - po := &client.PatchOptions{} - client.DryRunAll.ApplyToPatch(po) - all := []string{metav1.DryRunAll} - Expect(po.AsPatchOptions().DryRun).To(Equal(all)) - }) + PIt("should fail if the object cannot be mapped to a GVK", func() { - It("should allow setting Force to 'true'", func() { - po := &client.PatchOptions{} - client.ForceOwnership.ApplyToPatch(po) - mpo := po.AsPatchOptions() - Expect(mpo.Force).NotTo(BeNil()) - Expect(*mpo.Force).To(BeTrue()) }) - It("should allow setting the field manager", func() { - po := &client.PatchOptions{} - client.FieldOwner("some-owner").ApplyToPatch(po) - Expect(po.AsPatchOptions().FieldManager).To(Equal("some-owner")) - }) + PIt("should fail if the GVK cannot be mapped to a Resource", func() { - It("should produce empty metav1.PatchOptions if nil", func() { - var po *client.PatchOptions - Expect(po.AsPatchOptions()).To(Equal(&metav1.PatchOptions{})) - po = &client.PatchOptions{} - Expect(po.AsPatchOptions()).To(Equal(&metav1.PatchOptions{})) }) }) -}) +} var _ = Describe("ClientWithCache", func() { Describe("Get", func() { diff --git a/pkg/client/dryrun_test.go b/pkg/client/dryrun_test.go index 0d370e0576..bedc88564e 100644 --- a/pkg/client/dryrun_test.go +++ b/pkg/client/dryrun_test.go @@ -73,7 +73,7 @@ var _ = Describe("DryRunClient", func() { }) AfterEach(func() { - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) }) It("should successfully Get an object", func() { diff --git a/pkg/client/namespaced_client_test.go b/pkg/client/namespaced_client_test.go index 6e1c2641a3..f6dba4682f 100644 --- a/pkg/client/namespaced_client_test.go +++ b/pkg/client/namespaced_client_test.go @@ -86,7 +86,7 @@ var _ = Describe("NamespacedClient", func() { }) AfterEach(func() { - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) }) It("should successfully Get a namespace-scoped object", func() { name := types.NamespacedName{Name: dep.Name} @@ -113,7 +113,7 @@ var _ = Describe("NamespacedClient", func() { }) AfterEach(func() { - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) }) It("should successfully List objects when namespace is not specified with the object", func() { @@ -137,7 +137,7 @@ var _ = Describe("NamespacedClient", func() { Describe("Create", func() { AfterEach(func() { - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) }) It("should successfully create object in the right namespace", func() { @@ -192,7 +192,7 @@ var _ = Describe("NamespacedClient", func() { Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) }) It("should successfully update the provided object", func() { @@ -262,8 +262,8 @@ var _ = Describe("NamespacedClient", func() { err = getClient().Update(ctx, changedDep) Expect(err).To(HaveOccurred()) - deleteDeployment(ctx, changedDep, tns.Name) - deleteNamespace(ctx, tns) + deleteDeployment(ctx, clientset, changedDep, tns.Name) + deleteNamespace(ctx, clientset, tns) }) It("should update a cluster scoped resource", func() { @@ -308,7 +308,7 @@ var _ = Describe("NamespacedClient", func() { }) AfterEach(func() { - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) }) It("should successfully modify the object using Patch", func() { @@ -373,8 +373,8 @@ var _ = Describe("NamespacedClient", func() { err = getClient().Patch(ctx, changedDep, client.RawPatch(types.MergePatchType, generatePatch())) Expect(err).To(HaveOccurred()) - deleteDeployment(ctx, changedDep, tns.Name) - deleteNamespace(ctx, tns) + deleteDeployment(ctx, clientset, changedDep, tns.Name) + deleteNamespace(ctx, clientset, tns) }) It("should successfully modify cluster scoped resource", func() { @@ -417,7 +417,7 @@ var _ = Describe("NamespacedClient", func() { }) AfterEach(func() { - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) }) It("should successfully delete an object when namespace is not specified", func() { By("deleting the object") @@ -475,8 +475,8 @@ var _ = Describe("NamespacedClient", func() { Expect(err).NotTo(HaveOccurred()) Expect(actual).To(BeEquivalentTo(changedDep)) - deleteDeployment(ctx, changedDep, tns.Name) - deleteNamespace(ctx, tns) + deleteDeployment(ctx, clientset, changedDep, tns.Name) + deleteNamespace(ctx, clientset, tns) }) }) @@ -488,7 +488,7 @@ var _ = Describe("NamespacedClient", func() { }) AfterEach(func() { - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) }) It("should change objects via update status", func() { diff --git a/pkg/client/typed_client.go b/pkg/client/typed_client.go index 92afd9a9c2..68d728e4aa 100644 --- a/pkg/client/typed_client.go +++ b/pkg/client/typed_client.go @@ -18,8 +18,11 @@ package client import ( "context" + "fmt" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/util/watchlist" + "sigs.k8s.io/controller-runtime/pkg/log" ) var _ Reader = &typedClient{} @@ -157,6 +160,21 @@ func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOpti listOpts := ListOptions{} listOpts.ApplyOptions(opts) + if watchListOptions, hasWatchListOptionsPrepared, watchListOptionsErr := watchlist.PrepareWatchListOptionsFromListOptions(*listOpts.AsListOptions()); watchListOptionsErr != nil { + log.FromContext(ctx).Error(watchListOptionsErr, fmt.Sprintf("Warning: Failed preparing watchlist options for %s, falling back to the standard LIST semantics", r.resource())) + } else if hasWatchListOptionsPrepared { + err := r.Get(). + NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()). + Resource(r.resource()). + VersionedParams(&watchListOptions, c.paramCodec). + WatchList(ctx). + Into(obj) + if err == nil { + return nil + } + log.FromContext(ctx).Error(err, fmt.Sprintf("Warning: The watchlist request for %s ended with an error, falling back to the standard LIST semantics", r.resource())) + } + return r.Get(). NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()). Resource(r.resource()). diff --git a/pkg/client/unstructured_client.go b/pkg/client/unstructured_client.go index 0d96951780..581fc28d8a 100644 --- a/pkg/client/unstructured_client.go +++ b/pkg/client/unstructured_client.go @@ -22,6 +22,8 @@ import ( "strings" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/util/watchlist" + "sigs.k8s.io/controller-runtime/pkg/log" ) var _ Reader = &unstructuredClient{} @@ -214,6 +216,21 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ... listOpts := ListOptions{} listOpts.ApplyOptions(opts) + if watchListOptions, hasWatchListOptionsPrepared, watchListOptionsErr := watchlist.PrepareWatchListOptionsFromListOptions(*listOpts.AsListOptions()); watchListOptionsErr != nil { + log.FromContext(ctx).Error(watchListOptionsErr, fmt.Sprintf("Warning: Failed preparing watchlist options for %s, falling back to the standard LIST semantics", r.resource())) + } else if hasWatchListOptionsPrepared { + err := r.Get(). + NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()). + Resource(r.resource()). + VersionedParams(&watchListOptions, uc.paramCodec). + WatchList(ctx). + Into(obj) + if err == nil { + return nil + } + log.FromContext(ctx).Error(err, fmt.Sprintf("Warning: The watchlist request for %s ended with an error, falling back to the standard LIST semantics", r.resource())) + } + return r.Get(). NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()). Resource(r.resource()). diff --git a/pkg/client/watch_test.go b/pkg/client/watch_test.go index 26d90f6550..f4b94f21ec 100644 --- a/pkg/client/watch_test.go +++ b/pkg/client/watch_test.go @@ -62,7 +62,7 @@ var _ = Describe("ClientWithWatch", func() { }) AfterEach(func() { - deleteDeployment(ctx, dep, ns) + deleteDeployment(ctx, clientset, dep, ns) }) Describe("NewWithWatch", func() { From 48dcfc4b96350f58b383dff0ee375fc2eaeb48a9 Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Mon, 3 Mar 2025 17:39:53 +0100 Subject: [PATCH 2/3] Fix review finding --- pkg/client/client_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index ef4bd75c75..2ed390f99d 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -2618,7 +2618,6 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { Expect(deps.Items).To(BeEmpty()) }) - // TODO(seans): get label selector test working It("should filter results by label selector", func() { By("creating a Deployment with the app=frontend label") depFrontend := &appsv1.Deployment{ @@ -3097,7 +3096,6 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { Expect(deps.Items).To(BeEmpty()) }) - // TODO(seans): get label selector test working It("should filter results by label selector", func() { By("creating a Deployment with the app=frontend label") depFrontend := &appsv1.Deployment{ @@ -3569,7 +3567,6 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { Expect(metaList.Items).To(BeEmpty()) }) - // TODO(seans): get label selector test working It("should filter results by label selector", func() { By("creating a Deployment with the app=frontend label") depFrontend := &appsv1.Deployment{ From ea98b9ae9f9901c15d7c919c1c17683a8a10fd2d Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Sat, 8 Mar 2025 11:03:02 +0100 Subject: [PATCH 3/3] Use minimal scheme for all unstructured & metadata tests --- pkg/client/client_test.go | 110 +++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 2ed390f99d..8e4bc743ba 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -493,7 +493,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC Context("with unstructured objects", func() { It("should create a new object from a go struct", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -516,7 +516,9 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should create a new non-namespace object ", func() { - cl, err := client.New(cfg, client.Options{}) + scheme := runtime.NewScheme() + Expect(corev1.AddToScheme(scheme)).To(Succeed()) + cl, err := client.New(cfg, client.Options{Scheme: scheme}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -545,7 +547,9 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should fail if the object already exists", func() { - cl, err := client.New(cfg, client.Options{}) + scheme := runtime.NewScheme() + Expect(appsv1.AddToScheme(scheme)).To(Succeed()) + cl, err := client.New(cfg, client.Options{Scheme: scheme}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -574,7 +578,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should fail if the object does not pass server-side validation", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -596,7 +600,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC Context("with metadata objects", func() { It("should fail with an error", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) obj := metaOnlyFromObj(dep, scheme) @@ -606,7 +610,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC Context("with the DryRun option", func() { It("should not create a new object from a go struct", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -734,7 +738,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) Context("with unstructured objects", func() { It("should update an existing object from a go struct", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -762,7 +766,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should update and preserve type information", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -783,7 +787,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should update an existing object non-namespace object from a go struct", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -809,7 +813,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC Expect(actual.Annotations["foo"]).To(Equal("bar")) }) It("should fail if the object does not exist", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -823,7 +827,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) Context("with metadata objects", func() { It("should fail with an error", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) obj := metaOnlyFromObj(dep, scheme) @@ -836,7 +840,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC Describe("Patch", func() { Context("Metadata Client", func() { It("should merge patch with options", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1434,7 +1438,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC Context("with unstructured objects", func() { It("should update status of an existing object", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1457,7 +1461,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should update status and preserve type information", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1477,7 +1481,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should patch status and preserve type information", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1504,7 +1508,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should not update spec of an existing object", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1530,7 +1534,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should update an existing object non-namespace object", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1552,7 +1556,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should fail if the object does not exist", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1575,7 +1579,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC Context("with metadata objects", func() { It("should fail to update with an error", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) obj := metaOnlyFromObj(dep, scheme) @@ -1583,7 +1587,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should patch status and preserve type information", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1716,7 +1720,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) Context("with unstructured objects", func() { It("should delete an existing object from a go struct", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1742,7 +1746,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should delete an existing object non-namespace object from a go struct", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1768,7 +1772,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should fail if the object does not exist", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1785,7 +1789,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should delete a collection of object", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1822,7 +1826,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) Context("with metadata objects", func() { It("should delete an existing object from a go struct", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1841,7 +1845,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should delete an existing object non-namespace object from a go struct", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1860,7 +1864,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should fail if the object does not exist", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -1871,7 +1875,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should delete a collection of object", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -2015,7 +2019,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -2048,7 +2052,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC var u runtime.Unstructured = &unstructured.Unstructured{} Expect(scheme.Convert(node, u, nil)).To(Succeed()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -2069,7 +2073,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should fail if the object does not exist", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -2082,7 +2086,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC It("should not retain any data in the obj variable that is not on the server", func() { object := &unstructured.Unstructured{} - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -2110,7 +2114,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -2139,7 +2143,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC node, err := clientset.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -2158,7 +2162,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should fail if the object does not exist", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -2183,7 +2187,9 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) It("should not retain any data in the obj variable that is not on the server", func() { - cl, err := client.New(cfg, client.Options{}) + scheme := runtime.NewScheme() + Expect(pkg.AddToScheme(scheme)).To(Succeed()) + cl, err := client.New(cfg, client.Options{Scheme: scheme}) Expect(err).NotTo(HaveOccurred()) Expect(cl).NotTo(BeNil()) @@ -2983,7 +2989,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) var logs []string @@ -3080,7 +3086,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { }) It("should return an empty list if there are no matching objects", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments in the cluster") @@ -3137,7 +3143,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments with label app=backend") @@ -3200,7 +3206,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { depBackend, err = clientset.AppsV1().Deployments("test-namespace-6").Create(ctx, depBackend, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments in test-namespace-5") @@ -3258,7 +3264,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments with field metadata.name=deployment-backend") @@ -3349,7 +3355,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { depFrontend4, err = clientset.AppsV1().Deployments("test-namespace-8").Create(ctx, depFrontend4, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments in test-namespace-8 with label app=frontend") @@ -3417,7 +3423,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { Expect(err).NotTo(HaveOccurred()) defer deleteDeployment(ctx, clientset, dep4, ns) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing 1 deployment when limit=1 is used") @@ -3486,7 +3492,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) var logs []string @@ -3551,7 +3557,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { }) It("should return an empty list if there are no matching objects", func() { - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments in the cluster") @@ -3608,7 +3614,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments with label app=backend") @@ -3671,7 +3677,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { depBackend, err = clientset.AppsV1().Deployments("test-namespace-2").Create(ctx, depBackend, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments in test-namespace-1") @@ -3729,7 +3735,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { depBackend, err = clientset.AppsV1().Deployments(ns).Create(ctx, depBackend, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments with field metadata.name=deployment-backend") @@ -3820,7 +3826,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { depFrontend4, err = clientset.AppsV1().Deployments("test-namespace-4").Create(ctx, depFrontend4, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing all Deployments in test-namespace-3 with label app=frontend") @@ -3890,7 +3896,7 @@ func ClientListTest(watchListEnabled, watchListClientEnabled bool) { Expect(err).NotTo(HaveOccurred()) defer deleteDeployment(ctx, clientset, dep4, ns) - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: runtime.NewScheme()}) Expect(err).NotTo(HaveOccurred()) By("listing 1 deployment when limit=1 is used") @@ -4014,6 +4020,7 @@ var _ = Describe("ClientWithCache", func() { Cache: &client.CacheOptions{ Reader: cachedReader, }, + Scheme: runtime.NewScheme(), }) Expect(err).NotTo(HaveOccurred()) @@ -4035,6 +4042,7 @@ var _ = Describe("ClientWithCache", func() { Reader: cachedReader, Unstructured: true, }, + Scheme: runtime.NewScheme(), }) Expect(err).NotTo(HaveOccurred()) @@ -4072,6 +4080,7 @@ var _ = Describe("ClientWithCache", func() { Cache: &client.CacheOptions{ Reader: cachedReader, }, + Scheme: runtime.NewScheme(), }) Expect(err).NotTo(HaveOccurred()) @@ -4091,6 +4100,7 @@ var _ = Describe("ClientWithCache", func() { Reader: cachedReader, Unstructured: true, }, + Scheme: runtime.NewScheme(), }) Expect(err).NotTo(HaveOccurred())