From b2b4d89501adaa4ca8621bdd633f3ac0443bd489 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Fri, 22 Dec 2023 00:08:01 +0100 Subject: [PATCH] Storage: Support grafana.app/folder field selector & label selectors (#79816) * support grafana.app/folder field selector & label selectors * lint fix --- pkg/apis/types.go | 8 +++ pkg/registry/apis/playlist/register.go | 23 ++++++++ .../storage/entity/storage.go | 56 +++++++++++++++++-- pkg/services/store/entity/key.go | 2 +- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/pkg/apis/types.go b/pkg/apis/types.go index fdb1ff9b65b..55ab7a0d703 100644 --- a/pkg/apis/types.go +++ b/pkg/apis/types.go @@ -49,6 +49,14 @@ func (info *ResourceInfo) GroupResource() schema.GroupResource { } } +func (info *ResourceInfo) GroupVersionKind() schema.GroupVersionKind { + return schema.GroupVersionKind{ + Group: info.group, + Version: info.version, + Kind: info.kind, + } +} + func (info *ResourceInfo) SingularGroupResource() schema.GroupResource { return schema.GroupResource{ Group: info.group, diff --git a/pkg/registry/apis/playlist/register.go b/pkg/registry/apis/playlist/register.go index b1dab9754f0..11a8aaf0a9c 100644 --- a/pkg/registry/apis/playlist/register.go +++ b/pkg/registry/apis/playlist/register.go @@ -2,6 +2,7 @@ package playlist import ( "fmt" + "strings" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -67,6 +68,28 @@ func (b *PlaylistAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { Version: runtime.APIVersionInternal, }) + gvk := playlist.PlaylistResourceInfo.GroupVersionKind() + + // Add playlist thing + _ = scheme.AddFieldLabelConversionFunc(gvk, + runtime.FieldLabelConversionFunc( + func(label, value string) (string, string, error) { + if strings.HasPrefix(label, "grafana.app/") { + return label, value, nil + } + + switch label { + case "metadata.name": + return label, value, nil + case "metadata.namespace": + return label, value, nil + default: + return "", "", fmt.Errorf("%q is not a known field selector: only %q, %q", label, "metadata.name", "metadata.namespace") + } + }, + ), + ) + // If multiple versions exist, then register conversions from zz_generated.conversion.go // if err := playlist.RegisterConversions(scheme); err != nil { // return err diff --git a/pkg/services/grafana-apiserver/storage/entity/storage.go b/pkg/services/grafana-apiserver/storage/entity/storage.go index 8055e9d7996..298f6364276 100644 --- a/pkg/services/grafana-apiserver/storage/entity/storage.go +++ b/pkg/services/grafana-apiserver/storage/entity/storage.go @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage" @@ -218,14 +219,53 @@ func (s *Storage) GetList(ctx context.Context, key string, opts storage.ListOpti return err } - rsp, err := s.store.List(ctx, &entityStore.EntityListRequest{ + req := &entityStore.EntityListRequest{ Key: []string{key}, WithBody: true, WithStatus: true, NextPageToken: opts.Predicate.Continue, Limit: opts.Predicate.Limit, + Labels: map[string]string{}, // TODO push label/field matching down to storage + } + + // translate "equals" label selectors to storage label conditions + requirements, selectable := opts.Predicate.Label.Requirements() + if !selectable { + return apierrors.NewBadRequest("label selector is not selectable") + } + + for _, r := range requirements { + if r.Operator() == selection.Equals { + req.Labels[r.Key()] = r.Values().List()[0] + } + } + + // translate grafana.app/folder field selector to folder condition + fields := opts.Predicate.Field.Requirements() + for _, f := range fields { + if f.Field == "grafana.app/folder" { + if f.Operator != selection.Equals { + return apierrors.NewBadRequest("grafana.app/folder field selector only supports equality") + } + + // select items in the spcified folder + req.Folder = f.Value + } + } + + // use Transform function to remove grafana.app/folder field selector + opts.Predicate.Field, err = opts.Predicate.Field.Transform(func(field, value string) (string, string, error) { + if field == "grafana.app/folder" { + return "", "", nil + } + return field, value, nil }) + if err != nil { + return err + } + + rsp, err := s.store.List(ctx, req) if err != nil { return apierrors.NewInternalError(err) } @@ -314,6 +354,15 @@ func (s *Storage) guaranteedUpdate( return err } + accessor, err := meta.Accessor(destination) + if err != nil { + return err + } + previousVersion, _ := strconv.ParseInt(accessor.GetResourceVersion(), 10, 64) + if preconditions != nil && preconditions.ResourceVersion != nil { + previousVersion, _ = strconv.ParseInt(*preconditions.ResourceVersion, 10, 64) + } + res := &storage.ResponseMeta{} updatedObj, _, err := tryUpdate(destination, *res) if err != nil { @@ -333,11 +382,6 @@ func (s *Storage) guaranteedUpdate( return err } - previousVersion := int64(0) - if preconditions != nil && preconditions.ResourceVersion != nil { - previousVersion, _ = strconv.ParseInt(*preconditions.ResourceVersion, 10, 64) - } - req := &entityStore.UpdateEntityRequest{ Entity: e, PreviousVersion: previousVersion, diff --git a/pkg/services/store/entity/key.go b/pkg/services/store/entity/key.go index 775f31860ce..2820c846d8f 100644 --- a/pkg/services/store/entity/key.go +++ b/pkg/services/store/entity/key.go @@ -42,7 +42,7 @@ func ParseKey(key string) (*Key, error) { } func (k *Key) String() string { - s := k.Group + "/" + k.Resource + "/" + k.Namespace + s := "/" + k.Group + "/" + k.Resource + "/" + k.Namespace if len(k.Name) > 0 { s += "/" + k.Name if len(k.Subresource) > 0 {