mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Storage: Add support for sortBy selector (#80680)
* add support for sortBy field selector * use label selectors instead of field selectors * set entity_labels on create & update * make entity server integration tests work * test fixes * be more consistent with handling of empty body, meta or status * workaround for database is locked errors during migration * fix double import of sqlite3 * rename functions and tidy up * refactor update * disable integration tests until we can fix the database locking issue
This commit is contained in:
@@ -277,12 +277,6 @@ func (s *service) start(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// support folder selection
|
||||
err = entitystorage.RegisterFieldSelectorSupport(Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the server
|
||||
server, err := serverConfig.Complete().New("grafana-apiserver", genericapiserver.NewEmptyDelegate())
|
||||
if err != nil {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
)
|
||||
|
||||
const folderAnnoKey = "grafana.app/folder"
|
||||
|
||||
type FieldRequirements struct {
|
||||
// Equals folder
|
||||
Folder *string
|
||||
}
|
||||
|
||||
func ReadFieldRequirements(selector fields.Selector) (FieldRequirements, fields.Selector, error) {
|
||||
requirements := FieldRequirements{}
|
||||
|
||||
if selector == nil {
|
||||
return requirements, selector, nil
|
||||
}
|
||||
|
||||
for _, r := range selector.Requirements() {
|
||||
switch r.Field {
|
||||
case folderAnnoKey:
|
||||
if (r.Operator != selection.Equals) && (r.Operator != selection.DoubleEquals) {
|
||||
return requirements, selector, apierrors.NewBadRequest("only equality is supported in the selectors")
|
||||
}
|
||||
folder := r.Value
|
||||
requirements.Folder = &folder
|
||||
}
|
||||
}
|
||||
|
||||
// use Transform function to remove grafana.app/folder field selector
|
||||
selector, err := selector.Transform(func(field, value string) (string, string, error) {
|
||||
switch field {
|
||||
case folderAnnoKey:
|
||||
return "", "", nil
|
||||
}
|
||||
return field, value, nil
|
||||
})
|
||||
|
||||
return requirements, selector, err
|
||||
}
|
||||
|
||||
func RegisterFieldSelectorSupport(scheme *runtime.Scheme) error {
|
||||
grafanaFieldSupport := runtime.FieldLabelConversionFunc(
|
||||
func(field, value string) (string, string, error) {
|
||||
if strings.HasPrefix(field, "grafana.app/") {
|
||||
return field, value, nil
|
||||
}
|
||||
return "", "", getBadSelectorError(field)
|
||||
},
|
||||
)
|
||||
|
||||
// Register all the internal types
|
||||
for gvk := range scheme.AllKnownTypes() {
|
||||
if strings.HasSuffix(gvk.Group, ".grafana.app") {
|
||||
err := scheme.AddFieldLabelConversionFunc(gvk, grafanaFieldSupport)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBadSelectorError(f string) error {
|
||||
return apierrors.NewBadRequest(
|
||||
fmt.Sprintf("%q is not a known field selector: only %q works", f, folderAnnoKey),
|
||||
)
|
||||
}
|
||||
49
pkg/services/apiserver/storage/entity/selector.go
Normal file
49
pkg/services/apiserver/storage/entity/selector.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
)
|
||||
|
||||
const folderAnnoKey = "grafana.app/folder"
|
||||
const sortByKey = "grafana.app/sortBy"
|
||||
|
||||
type Requirements struct {
|
||||
// Equals folder
|
||||
Folder *string
|
||||
// SortBy is a list of fields to sort by
|
||||
SortBy []string
|
||||
}
|
||||
|
||||
func ReadLabelSelectors(selector labels.Selector) (Requirements, labels.Selector, error) {
|
||||
requirements := Requirements{}
|
||||
newSelector := labels.NewSelector()
|
||||
|
||||
if selector == nil {
|
||||
return requirements, newSelector, nil
|
||||
}
|
||||
|
||||
labelSelectors, _ := selector.Requirements()
|
||||
|
||||
for _, r := range labelSelectors {
|
||||
switch r.Key() {
|
||||
case folderAnnoKey:
|
||||
if (r.Operator() != selection.Equals) && (r.Operator() != selection.DoubleEquals) {
|
||||
return requirements, newSelector, apierrors.NewBadRequest(folderAnnoKey + " label selector only supports equality")
|
||||
}
|
||||
folder := r.Values().List()[0]
|
||||
requirements.Folder = &folder
|
||||
case sortByKey:
|
||||
if r.Operator() != selection.In {
|
||||
return requirements, newSelector, apierrors.NewBadRequest(sortByKey + " label selector only supports in")
|
||||
}
|
||||
requirements.SortBy = r.Values().List()
|
||||
// add all unregonized label selectors to the new selector list, these will be processed by the entity store
|
||||
default:
|
||||
newSelector = newSelector.Add(r)
|
||||
}
|
||||
}
|
||||
|
||||
return requirements, newSelector, nil
|
||||
}
|
||||
@@ -229,29 +229,32 @@ func (s *Storage) GetList(ctx context.Context, key string, opts storage.ListOpti
|
||||
// TODO push label/field matching down to storage
|
||||
}
|
||||
|
||||
// translate grafana.app/* label selectors into field requirements
|
||||
requirements, newSelector, err := ReadLabelSelectors(opts.Predicate.Label)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if requirements.Folder != nil {
|
||||
req.Folder = *requirements.Folder
|
||||
}
|
||||
if len(requirements.SortBy) > 0 {
|
||||
req.Sort = requirements.SortBy
|
||||
}
|
||||
// Update the selector to remove the unneeded requirements
|
||||
opts.Predicate.Label = newSelector
|
||||
|
||||
// translate "equals" label selectors to storage label conditions
|
||||
requirements, selectable := opts.Predicate.Label.Requirements()
|
||||
labelRequirements, selectable := opts.Predicate.Label.Requirements()
|
||||
if !selectable {
|
||||
return apierrors.NewBadRequest("label selector is not selectable")
|
||||
}
|
||||
|
||||
for _, r := range requirements {
|
||||
for _, r := range labelRequirements {
|
||||
if r.Operator() == selection.Equals {
|
||||
req.Labels[r.Key()] = r.Values().List()[0]
|
||||
}
|
||||
}
|
||||
|
||||
// translate grafana.app/folder field selector to the folder condition
|
||||
fieldRequirements, fieldSelector, err := ReadFieldRequirements(opts.Predicate.Field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fieldRequirements.Folder != nil {
|
||||
req.Folder = *fieldRequirements.Folder
|
||||
}
|
||||
// Update the field selector to remove the unneeded selectors
|
||||
opts.Predicate.Field = fieldSelector
|
||||
|
||||
rsp, err := s.store.List(ctx, req)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
|
||||
Reference in New Issue
Block a user