From f6b2e6ea572034843e7019ac206e92073f9e6ca9 Mon Sep 17 00:00:00 2001 From: Danail Branekov Date: Fri, 21 Jul 2023 13:06:10 +0000 Subject: [PATCH] Filter by label selector when `GET /v3/service_instances` Co-authored-by: Danail Branekov Co-authored-by: Giuseppe Capizzi --- README.helm.md | 2 +- api/payloads/payloads_suite_test.go | 7 + api/payloads/service_binding_test.go | 123 ++++-------------- api/payloads/service_instance.go | 26 ++-- api/payloads/service_instance_test.go | 63 +++++++-- .../service_instance_repository.go | 10 +- .../service_instance_repository_test.go | 44 ++++++- tests/e2e/e2e_suite_test.go | 16 ++- tests/e2e/service_instances_test.go | 23 +++- 9 files changed, 185 insertions(+), 129 deletions(-) diff --git a/README.helm.md b/README.helm.md index 4ead23f4b..44d97c30a 100644 --- a/README.helm.md +++ b/README.helm.md @@ -114,4 +114,4 @@ Here are all the values that can be set for the chart: - `memory` (_String_): Memory limit. - `requests`: Resource requests. - `cpu` (_String_): CPU request. - - `memory` (_String_): Memory request. \ No newline at end of file + - `memory` (_String_): Memory request. diff --git a/api/payloads/payloads_suite_test.go b/api/payloads/payloads_suite_test.go index 070ae8383..134ced420 100644 --- a/api/payloads/payloads_suite_test.go +++ b/api/payloads/payloads_suite_test.go @@ -9,6 +9,7 @@ import ( apierrors "code.cloudfoundry.org/korifi/api/errors" "code.cloudfoundry.org/korifi/api/payloads/validation" "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/labels" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -71,3 +72,9 @@ func decodeQuery[T any, PT keyedPayload[T]](query string) (PT, error) { return actual, decodeErr } + +func parseLabelSelector(s string) labels.Selector { + reqs, err := labels.ParseToRequirements(s) + Expect(err).NotTo(HaveOccurred()) + return labels.NewSelector().Add(reqs...) +} diff --git a/api/payloads/service_binding_test.go b/api/payloads/service_binding_test.go index e20ebc968..293d0f76e 100644 --- a/api/payloads/service_binding_test.go +++ b/api/payloads/service_binding_test.go @@ -1,8 +1,6 @@ package payloads_test import ( - "net/http" - "code.cloudfoundry.org/korifi/api/errors" "code.cloudfoundry.org/korifi/api/payloads" "code.cloudfoundry.org/korifi/api/repositories" @@ -11,109 +9,34 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/gstruct" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" ) var _ = Describe("ServiceBindingList", func() { - decode := func(queryString string) (payloads.ServiceBindingList, error) { - serviceBindingList := payloads.ServiceBindingList{} - req, err := http.NewRequest("GET", "http://foo.com/bar"+queryString, nil) - Expect(err).NotTo(HaveOccurred()) - - err = validator.DecodeAndValidateURLValues(req, &serviceBindingList) - return serviceBindingList, err - } - - Describe("decode from url values", func() { - var ( - queryString string - serviceBindingList payloads.ServiceBindingList - decodeErr error - ) - - BeforeEach(func() { - queryString = "?app_guids=app_guid&service_instance_guids=service_instance_guid&include=include" - }) - - JustBeforeEach(func() { - serviceBindingList, decodeErr = decode(queryString) - }) + DescribeTable("valid query", + func(query string, expectedServiceBindingList payloads.ServiceBindingList) { + actualServiceBindingList, decodeErr := decodeQuery[payloads.ServiceBindingList](query) - It("succeeds", func() { Expect(decodeErr).NotTo(HaveOccurred()) - Expect(serviceBindingList).To(Equal(payloads.ServiceBindingList{ - AppGUIDs: "app_guid", - ServiceInstanceGUIDs: "service_instance_guid", - Include: "include", - LabelSelector: labels.Everything(), - })) - }) - - When("the query string contains an invalid label selector", func() { - BeforeEach(func() { - queryString = "?label_selector=~~~" - }) - - It("returns an error", func() { - Expect(decodeErr).To(MatchError(ContainSubstring("Invalid value"))) - }) - }) - }) - - DescribeTable("valid label selectors", - func(labelSelector string, verifyRequirements func(labels.Requirements)) { - serviceBindingList := payloads.ServiceBindingList{} - req, err := http.NewRequest("GET", "http://foo.com/bar?label_selector="+labelSelector, nil) - Expect(err).NotTo(HaveOccurred()) - err = validator.DecodeAndValidateURLValues(req, &serviceBindingList) - Expect(err).NotTo(HaveOccurred()) + Expect(*actualServiceBindingList).To(Equal(expectedServiceBindingList)) + }, + Entry("app_guids", "app_guids=app_guid", payloads.ServiceBindingList{AppGUIDs: "app_guid", LabelSelector: labels.Everything()}), + Entry("service_instance_guids", "service_instance_guids=si_guid", payloads.ServiceBindingList{ServiceInstanceGUIDs: "si_guid", LabelSelector: labels.Everything()}), + Entry("include", "include=include", payloads.ServiceBindingList{Include: "include", LabelSelector: labels.Everything()}), + Entry("label_selector=foo", "label_selector=foo", payloads.ServiceBindingList{LabelSelector: parseLabelSelector("foo")}), + Entry("label_selector=!foo", "label_selector=!foo", payloads.ServiceBindingList{LabelSelector: parseLabelSelector("!foo")}), + Entry("label_selector=foo=bar", "label_selector=foo=bar", payloads.ServiceBindingList{LabelSelector: parseLabelSelector("foo=bar")}), + Entry("label_selector=foo==bar", "label_selector=foo==bar", payloads.ServiceBindingList{LabelSelector: parseLabelSelector("foo==bar")}), + Entry("label_selector=foo!=bar", "label_selector=foo!=bar", payloads.ServiceBindingList{LabelSelector: parseLabelSelector("foo!=bar")}), + Entry("label_selector=foo in (bar1,bar2)", "label_selector=foo in (bar1,bar2)", payloads.ServiceBindingList{LabelSelector: parseLabelSelector("foo in (bar1, bar2)")}), + Entry("label_selector=foo notin (bar1,bar2)", "label_selector=foo notin (bar1,bar2)", payloads.ServiceBindingList{LabelSelector: parseLabelSelector("foo notin (bar1, bar2)")}), + ) - requirements, selectable := serviceBindingList.LabelSelector.Requirements() - Expect(selectable).To(BeTrue()) - verifyRequirements(requirements) + DescribeTable("invalid query", + func(query string, expectedErrMsg string) { + _, decodeErr := decodeQuery[payloads.ServiceBindingList](query) + Expect(decodeErr).To(MatchError(ContainSubstring(expectedErrMsg))) }, - Entry("foo exists", "foo", func(r labels.Requirements) { - Expect(r).To(HaveLen(1)) - Expect(r[0].Key()).To(Equal("foo")) - Expect(r[0].Operator()).To(Equal(selection.Exists)) - Expect(r[0].Values()).To(BeEmpty()) - }), - Entry("foo does not exist", "!foo", func(r labels.Requirements) { - Expect(r).To(HaveLen(1)) - Expect(r[0].Key()).To(Equal("foo")) - Expect(r[0].Operator()).To(Equal(selection.DoesNotExist)) - Expect(r[0].Values()).To(BeEmpty()) - }), - Entry("foo=bar", "foo=bar", func(r labels.Requirements) { - Expect(r).To(HaveLen(1)) - Expect(r[0].Key()).To(Equal("foo")) - Expect(r[0].Operator()).To(Equal(selection.Equals)) - Expect(r[0].Values()).To(SatisfyAll(HaveLen(1), HaveKey("bar"))) - }), - Entry("foo==bar", "foo==bar", func(r labels.Requirements) { - Expect(r).To(HaveLen(1)) - Expect(r[0].Key()).To(Equal("foo")) - Expect(r[0].Operator()).To(Equal(selection.DoubleEquals)) - Expect(r[0].Values()).To(SatisfyAll(HaveLen(1), HaveKey("bar"))) - }), - Entry("foo!=bar", "foo!=bar", func(r labels.Requirements) { - Expect(r).To(HaveLen(1)) - Expect(r[0].Key()).To(Equal("foo")) - Expect(r[0].Operator()).To(Equal(selection.NotEquals)) - Expect(r[0].Values()).To(SatisfyAll(HaveLen(1), HaveKey("bar"))) - }), - Entry("foo in (bar1,bar2)", "foo in (bar1,bar2)", func(r labels.Requirements) { - Expect(r).To(HaveLen(1)) - Expect(r[0].Key()).To(Equal("foo")) - Expect(r[0].Operator()).To(Equal(selection.In)) - Expect(r[0].Values()).To(SatisfyAll(HaveLen(2), HaveKey("bar1"), HaveKey("bar2"))) - }), - Entry("foo notin (bar1,bar2)", "foo notin (bar1,bar2)", func(r labels.Requirements) { - Expect(r).To(HaveLen(1)) - Expect(r[0].Key()).To(Equal("foo")) - Expect(r[0].Operator()).To(Equal(selection.NotIn)) - Expect(r[0].Values()).To(SatisfyAll(HaveLen(2), HaveKey("bar1"), HaveKey("bar2"))) - }), + Entry("invalid label_selector", "label_selector=~~~", "Invalid value"), ) Describe("ToMessage", func() { @@ -124,9 +47,7 @@ var _ = Describe("ServiceBindingList", func() { ) BeforeEach(func() { - fooBarRequirement, err := labels.NewRequirement("foo", selection.Equals, []string{"bar"}) - Expect(err).NotTo(HaveOccurred()) - labelSelector = labels.NewSelector().Add(*fooBarRequirement) + labelSelector = parseLabelSelector("foo=bar") payload = payloads.ServiceBindingList{ AppGUIDs: "app1,app2", diff --git a/api/payloads/service_instance.go b/api/payloads/service_instance.go index edb671945..b5d120722 100644 --- a/api/payloads/service_instance.go +++ b/api/payloads/service_instance.go @@ -11,6 +11,7 @@ import ( "code.cloudfoundry.org/korifi/api/payloads/validation" "code.cloudfoundry.org/korifi/api/repositories" jellidation "github.com/jellydator/validation" + "k8s.io/apimachinery/pkg/labels" ) type ServiceInstanceCreate struct { @@ -129,10 +130,11 @@ func (p *ServiceInstancePatch) UnmarshalJSON(data []byte) error { } type ServiceInstanceList struct { - Names string - GUIDs string - SpaceGUIDs string - OrderBy string + Names string + GUIDs string + SpaceGUIDs string + OrderBy string + LabelSelector labels.Selector } func (l ServiceInstanceList) Validate() error { @@ -143,14 +145,15 @@ func (l ServiceInstanceList) Validate() error { func (l *ServiceInstanceList) ToMessage() repositories.ListServiceInstanceMessage { return repositories.ListServiceInstanceMessage{ - Names: parse.ArrayParam(l.Names), - SpaceGUIDs: parse.ArrayParam(l.SpaceGUIDs), - GUIDs: parse.ArrayParam(l.GUIDs), + Names: parse.ArrayParam(l.Names), + SpaceGUIDs: parse.ArrayParam(l.SpaceGUIDs), + GUIDs: parse.ArrayParam(l.GUIDs), + LabelSelector: l.LabelSelector, } } func (l *ServiceInstanceList) SupportedKeys() []string { - return []string{"names", "space_guids", "guids", "order_by", "per_page", "page"} + return []string{"names", "space_guids", "guids", "order_by", "per_page", "page", "label_selector"} } func (l *ServiceInstanceList) IgnoredKeys() []*regexp.Regexp { @@ -162,5 +165,12 @@ func (l *ServiceInstanceList) DecodeFromURLValues(values url.Values) error { l.SpaceGUIDs = values.Get("space_guids") l.GUIDs = values.Get("guids") l.OrderBy = values.Get("order_by") + + labelSelectorRequirements, err := labels.ParseToRequirements(values.Get("label_selector")) + if err != nil { + return err + } + + l.LabelSelector = labels.NewSelector().Add(labelSelectorRequirements...) return nil } diff --git a/api/payloads/service_instance_test.go b/api/payloads/service_instance_test.go index 7d38f2989..22f9536c1 100644 --- a/api/payloads/service_instance_test.go +++ b/api/payloads/service_instance_test.go @@ -5,10 +5,12 @@ import ( "strings" "code.cloudfoundry.org/korifi/api/payloads" + "code.cloudfoundry.org/korifi/api/repositories" "code.cloudfoundry.org/korifi/tools" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" + "k8s.io/apimachinery/pkg/labels" ) var _ = Describe("ServiceInstanceList", func() { @@ -19,16 +21,23 @@ var _ = Describe("ServiceInstanceList", func() { Expect(decodeErr).NotTo(HaveOccurred()) Expect(*actualServiceInstanceList).To(Equal(expectedServiceInstanceList)) }, - Entry("names", "names=name", payloads.ServiceInstanceList{Names: "name"}), - Entry("space_guids", "space_guids=space_guid", payloads.ServiceInstanceList{SpaceGUIDs: "space_guid"}), - Entry("guids", "guids=guid", payloads.ServiceInstanceList{GUIDs: "guid"}), - Entry("created_at", "order_by=created_at", payloads.ServiceInstanceList{OrderBy: "created_at"}), - Entry("-created_at", "order_by=-created_at", payloads.ServiceInstanceList{OrderBy: "-created_at"}), - Entry("updated_at", "order_by=updated_at", payloads.ServiceInstanceList{OrderBy: "updated_at"}), - Entry("-updated_at", "order_by=-updated_at", payloads.ServiceInstanceList{OrderBy: "-updated_at"}), - Entry("name", "order_by=name", payloads.ServiceInstanceList{OrderBy: "name"}), - Entry("-name", "order_by=-name", payloads.ServiceInstanceList{OrderBy: "-name"}), - Entry("fields[xxx]", "fields[abc.d]=e", payloads.ServiceInstanceList{}), + Entry("names", "names=name", payloads.ServiceInstanceList{Names: "name", LabelSelector: labels.Everything()}), + Entry("space_guids", "space_guids=space_guid", payloads.ServiceInstanceList{SpaceGUIDs: "space_guid", LabelSelector: labels.Everything()}), + Entry("guids", "guids=guid", payloads.ServiceInstanceList{GUIDs: "guid", LabelSelector: labels.Everything()}), + Entry("created_at", "order_by=created_at", payloads.ServiceInstanceList{OrderBy: "created_at", LabelSelector: labels.Everything()}), + Entry("-created_at", "order_by=-created_at", payloads.ServiceInstanceList{OrderBy: "-created_at", LabelSelector: labels.Everything()}), + Entry("updated_at", "order_by=updated_at", payloads.ServiceInstanceList{OrderBy: "updated_at", LabelSelector: labels.Everything()}), + Entry("-updated_at", "order_by=-updated_at", payloads.ServiceInstanceList{OrderBy: "-updated_at", LabelSelector: labels.Everything()}), + Entry("name", "order_by=name", payloads.ServiceInstanceList{OrderBy: "name", LabelSelector: labels.Everything()}), + Entry("-name", "order_by=-name", payloads.ServiceInstanceList{OrderBy: "-name", LabelSelector: labels.Everything()}), + Entry("fields[xxx]", "fields[abc.d]=e", payloads.ServiceInstanceList{LabelSelector: labels.Everything()}), + Entry("label_selector=foo", "label_selector=foo", payloads.ServiceInstanceList{LabelSelector: parseLabelSelector("foo")}), + Entry("label_selector=!foo", "label_selector=!foo", payloads.ServiceInstanceList{LabelSelector: parseLabelSelector("!foo")}), + Entry("label_selector=foo=bar", "label_selector=foo=bar", payloads.ServiceInstanceList{LabelSelector: parseLabelSelector("foo=bar")}), + Entry("label_selector=foo==bar", "label_selector=foo==bar", payloads.ServiceInstanceList{LabelSelector: parseLabelSelector("foo==bar")}), + Entry("label_selector=foo!=bar", "label_selector=foo!=bar", payloads.ServiceInstanceList{LabelSelector: parseLabelSelector("foo!=bar")}), + Entry("label_selector=foo in (bar1,bar2)", "label_selector=foo in (bar1,bar2)", payloads.ServiceInstanceList{LabelSelector: parseLabelSelector("foo in (bar1, bar2)")}), + Entry("label_selector=foo notin (bar1,bar2)", "label_selector=foo notin (bar1,bar2)", payloads.ServiceInstanceList{LabelSelector: parseLabelSelector("foo notin (bar1, bar2)")}), ) DescribeTable("invalid query", @@ -37,7 +46,41 @@ var _ = Describe("ServiceInstanceList", func() { Expect(decodeErr).To(MatchError(ContainSubstring(expectedErrMsg))) }, Entry("invalid order_by", "order_by=foo", "value must be one of"), + Entry("invalid label_selector", "label_selector=~~~", "Invalid value"), ) + + Describe("ToMessage", func() { + var ( + payload payloads.ServiceInstanceList + message repositories.ListServiceInstanceMessage + labelSelector labels.Selector + ) + + BeforeEach(func() { + labelSelector = parseLabelSelector("foo=bar") + + payload = payloads.ServiceInstanceList{ + Names: "n1,n2", + GUIDs: "g1,g2", + SpaceGUIDs: "sg1,sg2", + OrderBy: "order", + LabelSelector: labelSelector, + } + }) + + JustBeforeEach(func() { + message = payload.ToMessage() + }) + + It("returns a list service instances message", func() { + Expect(message).To(Equal(repositories.ListServiceInstanceMessage{ + Names: []string{"n1", "n2"}, + SpaceGUIDs: []string{"sg1", "sg2"}, + GUIDs: []string{"g1", "g2"}, + LabelSelector: labelSelector, + })) + }) + }) }) var _ = Describe("ServiceInstanceCreate", func() { diff --git a/api/repositories/service_instance_repository.go b/api/repositories/service_instance_repository.go index 14c1b62cd..cdf6f6d5c 100644 --- a/api/repositories/service_instance_repository.go +++ b/api/repositories/service_instance_repository.go @@ -14,6 +14,7 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -76,9 +77,10 @@ func (p PatchServiceInstanceMessage) Apply(cfServiceInstance *korifiv1alpha1.CFS } type ListServiceInstanceMessage struct { - Names []string - SpaceGUIDs []string - GUIDs []string + Names []string + SpaceGUIDs []string + GUIDs []string + LabelSelector labels.Selector } type DeleteServiceInstanceMessage struct { @@ -210,7 +212,7 @@ func (r *ServiceInstanceRepo) ListServiceInstances(ctx context.Context, authInfo } serviceInstanceList := new(korifiv1alpha1.CFServiceInstanceList) - err = userClient.List(ctx, serviceInstanceList, client.InNamespace(ns)) + err = userClient.List(ctx, serviceInstanceList, client.InNamespace(ns), &client.ListOptions{LabelSelector: message.LabelSelector}) if k8serrors.IsForbidden(err) { continue } diff --git a/api/repositories/service_instance_repository_test.go b/api/repositories/service_instance_repository_test.go index 2846d49f8..2e291a6fe 100644 --- a/api/repositories/service_instance_repository_test.go +++ b/api/repositories/service_instance_repository_test.go @@ -10,6 +10,7 @@ import ( "code.cloudfoundry.org/korifi/api/repositories" korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" "code.cloudfoundry.org/korifi/tools" + "code.cloudfoundry.org/korifi/tools/k8s" "sigs.k8s.io/controller-runtime/pkg/client" . "github.com/onsi/ginkgo/v2" @@ -372,9 +373,9 @@ var _ = Describe("ServiceInstanceRepository", func() { &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: nonCFNamespace}}, )).To(Succeed()) - cfServiceInstance1 = createServiceInstanceCR(testCtx, k8sClient, prefixedGUID("service-instance"), space.Name, "service-instance-1", prefixedGUID("secret")) - cfServiceInstance2 = createServiceInstanceCR(testCtx, k8sClient, prefixedGUID("service-instance"), space2.Name, "service-instance-2", prefixedGUID("secret")) - cfServiceInstance3 = createServiceInstanceCR(testCtx, k8sClient, prefixedGUID("service-instance"), space3.Name, "service-instance-3", prefixedGUID("secret")) + cfServiceInstance1 = createServiceInstanceCR(testCtx, k8sClient, prefixedGUID("service-instance-1"), space.Name, "service-instance-1", prefixedGUID("secret")) + cfServiceInstance2 = createServiceInstanceCR(testCtx, k8sClient, prefixedGUID("service-instance-2"), space2.Name, "service-instance-2", prefixedGUID("secret")) + cfServiceInstance3 = createServiceInstanceCR(testCtx, k8sClient, prefixedGUID("service-instance-3"), space3.Name, "service-instance-3", prefixedGUID("secret")) createServiceInstanceCR(testCtx, k8sClient, prefixedGUID("service-instance"), nonCFNamespace, "service-instance-4", prefixedGUID("secret")) filters = repositories.ListServiceInstanceMessage{} @@ -461,6 +462,43 @@ var _ = Describe("ServiceInstanceRepository", func() { )) }) }) + + When("filtered by label selector", func() { + BeforeEach(func() { + Expect(k8s.PatchResource(ctx, k8sClient, cfServiceInstance1, func() { + cfServiceInstance1.Labels = map[string]string{"foo": "FOO1"} + })).To(Succeed()) + Expect(k8s.PatchResource(ctx, k8sClient, cfServiceInstance2, func() { + cfServiceInstance2.Labels = map[string]string{"foo": "FOO2"} + })).To(Succeed()) + Expect(k8s.PatchResource(ctx, k8sClient, cfServiceInstance3, func() { + cfServiceInstance3.Labels = map[string]string{"not_foo": "NOT_FOO"} + })).To(Succeed()) + }) + + DescribeTable("valid label selectors", + func(selector string, serviceBindingGUIDPrefixes ...string) { + serviceInstances, err := serviceInstanceRepo.ListServiceInstances(context.Background(), authInfo, repositories.ListServiceInstanceMessage{ + LabelSelector: labelSelector(selector), + }) + Expect(err).NotTo(HaveOccurred()) + + matchers := []any{} + for _, prefix := range serviceBindingGUIDPrefixes { + matchers = append(matchers, MatchFields(IgnoreExtras, Fields{"GUID": HavePrefix(prefix)})) + } + + Expect(serviceInstances).To(ConsistOf(matchers...)) + }, + Entry("key", "foo", "service-instance-1", "service-instance-2"), + Entry("!key", "!foo", "service-instance-3"), + Entry("key=value", "foo=FOO1", "service-instance-1"), + Entry("key==value", "foo==FOO2", "service-instance-2"), + Entry("key!=value", "foo!=FOO1", "service-instance-2", "service-instance-3"), + Entry("key in (value1,value2)", "foo in (FOO1,FOO2)", "service-instance-1", "service-instance-2"), + Entry("key notin (value1,value2)", "foo notin (FOO2)", "service-instance-1", "service-instance-3"), + ) + }) }) }) diff --git a/tests/e2e/e2e_suite_test.go b/tests/e2e/e2e_suite_test.go index c41de208d..3b8a9c527 100644 --- a/tests/e2e/e2e_suite_test.go +++ b/tests/e2e/e2e_suite_test.go @@ -717,6 +717,20 @@ func createServiceBinding(appGUID, instanceGUID, bindingName string) string { } func addServiceBindingLabels(bindingGUID string, labels map[string]string) { + GinkgoHelper() + + addLabels("/v3/service_credential_bindings/"+bindingGUID, labels) +} + +func addServiceInstanceLabels(serviceInstanceGUID string, labels map[string]string) { + GinkgoHelper() + + addLabels("/v3/service_instances/"+serviceInstanceGUID, labels) +} + +func addLabels(resourcePath string, labels map[string]string) { + GinkgoHelper() + var respResource responseResource resp, err := adminClient.R(). SetBody(metadataResource{ @@ -725,7 +739,7 @@ func addServiceBindingLabels(bindingGUID string, labels map[string]string) { }, }). SetResult(&respResource). - Patch("/v3/service_credential_bindings/" + bindingGUID) + Patch(resourcePath) Expect(err).NotTo(HaveOccurred()) Expect(resp).To(HaveRestyStatusCode(http.StatusOK)) } diff --git a/tests/e2e/service_instances_test.go b/tests/e2e/service_instances_test.go index 7d571fedd..f5fc383b3 100644 --- a/tests/e2e/service_instances_test.go +++ b/tests/e2e/service_instances_test.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/go-resty/resty/v2" + "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" @@ -183,16 +184,18 @@ var _ = Describe("Service Instances", func() { anotherSpaceGUID string anotherInstanceGUID string serviceInstancesList resourceList[resource] + queryString string ) BeforeEach(func() { + queryString = "" anotherSpaceGUID = createSpace(generateGUID("space1"), commonTestOrgGUID) anotherInstanceGUID = createServiceInstance(anotherSpaceGUID, generateGUID("service-instance"), nil) }) JustBeforeEach(func() { serviceInstancesList = resourceList[resource]{} - httpResp, httpError = certClient.R().SetResult(&serviceInstancesList).Get("/v3/service_instances") + httpResp, httpError = certClient.R().SetResult(&serviceInstancesList).Get("/v3/service_instances" + queryString) }) It("does not return service instances in spaces the user is not permitted", func() { @@ -226,6 +229,24 @@ var _ = Describe("Service Instances", func() { }), )) }) + + When("label selector is specified on the search query", func() { + BeforeEach(func() { + label := uuid.NewString() + queryString = "?label_selector=" + label + addServiceInstanceLabels(existingInstanceGUID, map[string]string{label: ""}) + }) + + It("lists service instances matching the label selector", func() { + Expect(httpError).NotTo(HaveOccurred()) + Expect(httpResp).To(HaveRestyStatusCode(http.StatusOK)) + Expect(serviceInstancesList.Resources).To(ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "GUID": Equal(existingInstanceGUID), + }), + )) + }) + }) }) }) })