Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter apps by label selector #2724

Merged
merged 3 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions api/payloads/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@ func (d AppSetCurrentDroplet) Validate() error {
}

type AppList struct {
Names string
GUIDs string
SpaceGuids string
OrderBy string
Names string
GUIDs string
SpaceGuids string
OrderBy string
LabelSelector string
}

func (a AppList) Validate() error {
Expand All @@ -95,21 +96,23 @@ func (a AppList) Validate() error {

func (a *AppList) ToMessage() repositories.ListAppsMessage {
return repositories.ListAppsMessage{
Names: parse.ArrayParam(a.Names),
Guids: parse.ArrayParam(a.GUIDs),
SpaceGuids: parse.ArrayParam(a.SpaceGuids),
Names: parse.ArrayParam(a.Names),
Guids: parse.ArrayParam(a.GUIDs),
SpaceGuids: parse.ArrayParam(a.SpaceGuids),
LabelSelector: a.LabelSelector,
}
}

func (a *AppList) SupportedKeys() []string {
return []string{"names", "guids", "space_guids", "order_by", "per_page", "page"}
return []string{"names", "guids", "space_guids", "order_by", "per_page", "page", "label_selector"}
}

func (a *AppList) DecodeFromURLValues(values url.Values) error {
a.Names = values.Get("names")
a.GUIDs = values.Get("guids")
a.SpaceGuids = values.Get("space_guids")
a.OrderBy = values.Get("order_by")
a.LabelSelector = values.Get("label_selector")
return nil
}

Expand Down
17 changes: 10 additions & 7 deletions api/payloads/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var _ = Describe("AppList", func() {
Entry("order_by -name", "order_by=-name", payloads.AppList{OrderBy: "-name"}),
Entry("order_by state", "order_by=state", payloads.AppList{OrderBy: "state"}),
Entry("order_by -state", "order_by=-state", payloads.AppList{OrderBy: "-state"}),
Entry("label_selector=foo", "label_selector=foo", payloads.AppList{LabelSelector: "foo"}),
)

DescribeTable("invalid query",
Expand All @@ -44,15 +45,17 @@ var _ = Describe("AppList", func() {
Describe("ToMessage", func() {
It("translates to repository message", func() {
appList := payloads.AppList{
Names: "n1,n2",
GUIDs: "g1,g2",
SpaceGuids: "s1,s2",
OrderBy: "created_at",
Names: "n1,n2",
GUIDs: "g1,g2",
SpaceGuids: "s1,s2",
OrderBy: "created_at",
LabelSelector: "foo=bar",
}
Expect(appList.ToMessage()).To(Equal(repositories.ListAppsMessage{
Names: []string{"n1", "n2"},
Guids: []string{"g1", "g2"},
SpaceGuids: []string{"s1", "s2"},
Names: []string{"n1", "n2"},
Guids: []string{"g1", "g2"},
SpaceGuids: []string{"s1", "s2"},
LabelSelector: "foo=bar",
}))
})
})
Expand Down
7 changes: 0 additions & 7 deletions api/payloads/payloads_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ 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"
Expand Down Expand Up @@ -72,9 +71,3 @@ 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...)
}
12 changes: 2 additions & 10 deletions api/payloads/service_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ 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 ServiceBindingCreate struct {
Expand Down Expand Up @@ -48,7 +47,7 @@ type ServiceBindingList struct {
AppGUIDs string
ServiceInstanceGUIDs string
Include string
LabelSelector labels.Selector
LabelSelector string
}

func (l *ServiceBindingList) ToMessage() repositories.ListServiceBindingsMessage {
Expand All @@ -67,14 +66,7 @@ func (l *ServiceBindingList) DecodeFromURLValues(values url.Values) error {
l.AppGUIDs = values.Get("app_guids")
l.ServiceInstanceGUIDs = values.Get("service_instance_guids")
l.Include = values.Get("include")

labelSelectorRequirements, err := labels.ParseToRequirements(values.Get("label_selector"))
if err != nil {
return err
}

l.LabelSelector = labels.NewSelector().Add(labelSelectorRequirements...)

l.LabelSelector = values.Get("label_selector")
return nil
}

Expand Down
34 changes: 8 additions & 26 deletions api/payloads/service_binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gstruct"
"k8s.io/apimachinery/pkg/labels"
)

var _ = Describe("ServiceBindingList", func() {
Expand All @@ -19,41 +18,24 @@ var _ = Describe("ServiceBindingList", func() {
Expect(decodeErr).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)")}),
)

DescribeTable("invalid query",
func(query string, expectedErrMsg string) {
_, decodeErr := decodeQuery[payloads.ServiceBindingList](query)
Expect(decodeErr).To(MatchError(ContainSubstring(expectedErrMsg)))
},
Entry("invalid label_selector", "label_selector=~~~", "Invalid value"),
Entry("app_guids", "app_guids=app_guid", payloads.ServiceBindingList{AppGUIDs: "app_guid"}),
Entry("service_instance_guids", "service_instance_guids=si_guid", payloads.ServiceBindingList{ServiceInstanceGUIDs: "si_guid"}),
Entry("include", "include=include", payloads.ServiceBindingList{Include: "include"}),
Entry("label_selector=foo", "label_selector=foo", payloads.ServiceBindingList{LabelSelector: "foo"}),
)

Describe("ToMessage", func() {
var (
payload payloads.ServiceBindingList
message repositories.ListServiceBindingsMessage
labelSelector labels.Selector
payload payloads.ServiceBindingList
message repositories.ListServiceBindingsMessage
)

BeforeEach(func() {
labelSelector = parseLabelSelector("foo=bar")

payload = payloads.ServiceBindingList{
AppGUIDs: "app1,app2",
ServiceInstanceGUIDs: "s1,s2",
Include: "include",
LabelSelector: labelSelector,
LabelSelector: "foo=bar",
}
})

Expand All @@ -65,7 +47,7 @@ var _ = Describe("ServiceBindingList", func() {
Expect(message).To(Equal(repositories.ListServiceBindingsMessage{
AppGUIDs: []string{"app1", "app2"},
ServiceInstanceGUIDs: []string{"s1", "s2"},
LabelSelector: labelSelector,
LabelSelector: "foo=bar",
}))
})
})
Expand Down
11 changes: 2 additions & 9 deletions api/payloads/service_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ 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 {
Expand Down Expand Up @@ -134,7 +133,7 @@ type ServiceInstanceList struct {
GUIDs string
SpaceGUIDs string
OrderBy string
LabelSelector labels.Selector
LabelSelector string
}

func (l ServiceInstanceList) Validate() error {
Expand Down Expand Up @@ -165,12 +164,6 @@ 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...)
l.LabelSelector = values.Get("label_selector")
return nil
}
41 changes: 15 additions & 26 deletions api/payloads/service_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gstruct"
"k8s.io/apimachinery/pkg/labels"
)

var _ = Describe("ServiceInstanceList", func() {
Expand All @@ -21,23 +20,17 @@ var _ = Describe("ServiceInstanceList", func() {
Expect(decodeErr).NotTo(HaveOccurred())
Expect(*actualServiceInstanceList).To(Equal(expectedServiceInstanceList))
},
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)")}),
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("label_selector=foo", "label_selector=foo", payloads.ServiceInstanceList{LabelSelector: "foo"}),
)

DescribeTable("invalid query",
Expand All @@ -46,25 +39,21 @@ 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
payload payloads.ServiceInstanceList
message repositories.ListServiceInstanceMessage
)

BeforeEach(func() {
labelSelector = parseLabelSelector("foo=bar")

payload = payloads.ServiceInstanceList{
Names: "n1,n2",
GUIDs: "g1,g2",
SpaceGUIDs: "sg1,sg2",
OrderBy: "order",
LabelSelector: labelSelector,
LabelSelector: "foo=bar",
}
})

Expand All @@ -77,7 +66,7 @@ var _ = Describe("ServiceInstanceList", func() {
Names: []string{"n1", "n2"},
SpaceGUIDs: []string{"sg1", "sg2"},
GUIDs: []string{"g1", "g2"},
LabelSelector: labelSelector,
LabelSelector: "foo=bar",
}))
})
})
Expand Down
15 changes: 11 additions & 4 deletions api/repositories/app_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
Expand Down Expand Up @@ -168,9 +169,10 @@ type SetAppDesiredStateMessage struct {
}

type ListAppsMessage struct {
Names []string
Guids []string
SpaceGuids []string
Names []string
Guids []string
SpaceGuids []string
LabelSelector string
}

type byName []AppRecord
Expand Down Expand Up @@ -315,6 +317,11 @@ func (f *AppRepo) ListApps(ctx context.Context, authInfo authorization.Info, mes
SetPredicate(message.Guids, func(s korifiv1alpha1.CFApp) string { return s.Name }),
}

labelSelector, err := labels.Parse(message.LabelSelector)
if err != nil {
return []AppRecord{}, apierrors.NewUnprocessableEntityError(err, "invalid label selector")
}

var filteredApps []korifiv1alpha1.CFApp
spaceGUIDSet := NewSet(message.SpaceGuids...)
for ns := range nsList {
Expand All @@ -323,7 +330,7 @@ func (f *AppRepo) ListApps(ctx context.Context, authInfo authorization.Info, mes
}

appList := &korifiv1alpha1.CFAppList{}
err := userClient.List(ctx, appList, client.InNamespace(ns))
err := userClient.List(ctx, appList, client.InNamespace(ns), &client.ListOptions{LabelSelector: labelSelector})

if k8serrors.IsForbidden(err) {
continue
Expand Down
Loading
Loading