mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Storage: Avoid relying on RequestInfo (#89635)
This commit is contained in:
parent
4cf3ebbb3d
commit
71270f3203
132
pkg/apiserver/registry/generic/key.go
Normal file
132
pkg/apiserver/registry/generic/key.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package generic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/api/validation/path"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
Group string
|
||||||
|
Resource string
|
||||||
|
Namespace string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseKey(key string) (*Key, error) {
|
||||||
|
// /<group>/<resource>[/namespaces/<namespace>][/<name>]
|
||||||
|
parts := strings.Split(key, "/")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return nil, fmt.Errorf("invalid key (expecting at least 2 parts): %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts[0] != "" {
|
||||||
|
return nil, fmt.Errorf("invalid key (expecting leading slash): %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := &Key{
|
||||||
|
Group: parts[1],
|
||||||
|
Resource: parts[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) == 3 {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts[3] != "namespaces" {
|
||||||
|
k.Name = parts[3]
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) < 5 {
|
||||||
|
return nil, fmt.Errorf("invalid key (expecting namespace after 'namespaces'): %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
k.Namespace = parts[4]
|
||||||
|
|
||||||
|
if len(parts) == 5 {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
k.Name = parts[5]
|
||||||
|
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) String() string {
|
||||||
|
s := "/" + k.Group + "/" + k.Resource
|
||||||
|
if len(k.Namespace) > 0 {
|
||||||
|
s += "/namespaces/" + k.Namespace
|
||||||
|
}
|
||||||
|
if len(k.Name) > 0 {
|
||||||
|
s += "/" + k.Name
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) IsEqual(other *Key) bool {
|
||||||
|
return k.Group == other.Group &&
|
||||||
|
k.Resource == other.Resource &&
|
||||||
|
k.Namespace == other.Namespace &&
|
||||||
|
k.Name == other.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyRootFunc is used by the generic registry store to construct the first portion of the storage key.
|
||||||
|
func KeyRootFunc(gr schema.GroupResource) func(ctx context.Context) string {
|
||||||
|
return func(ctx context.Context) string {
|
||||||
|
ns, _ := genericapirequest.NamespaceFrom(ctx)
|
||||||
|
key := &Key{
|
||||||
|
Group: gr.Group,
|
||||||
|
Resource: gr.Resource,
|
||||||
|
Namespace: ns,
|
||||||
|
}
|
||||||
|
return key.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceKeyFunc is the default function for constructing storage paths to
|
||||||
|
// a resource relative to the given prefix enforcing namespace rules. If the
|
||||||
|
// context does not contain a namespace, it errors.
|
||||||
|
func NamespaceKeyFunc(gr schema.GroupResource) func(ctx context.Context, name string) (string, error) {
|
||||||
|
return func(ctx context.Context, name string) (string, error) {
|
||||||
|
ns, ok := genericapirequest.NamespaceFrom(ctx)
|
||||||
|
if !ok || len(ns) == 0 {
|
||||||
|
return "", apierrors.NewBadRequest("Namespace parameter required.")
|
||||||
|
}
|
||||||
|
if len(name) == 0 {
|
||||||
|
return "", apierrors.NewBadRequest("Name parameter required.")
|
||||||
|
}
|
||||||
|
if msgs := path.IsValidPathSegmentName(name); len(msgs) != 0 {
|
||||||
|
return "", apierrors.NewBadRequest(fmt.Sprintf("Name parameter invalid: %q: %s", name, strings.Join(msgs, ";")))
|
||||||
|
}
|
||||||
|
key := &Key{
|
||||||
|
Group: gr.Group,
|
||||||
|
Resource: gr.Resource,
|
||||||
|
Namespace: ns,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
return key.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoNamespaceKeyFunc is the default function for constructing storage paths
|
||||||
|
// to a resource relative to the given prefix without a namespace.
|
||||||
|
func NoNamespaceKeyFunc(ctx context.Context, prefix string, gr schema.GroupResource, name string) (string, error) {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return "", apierrors.NewBadRequest("Name parameter required.")
|
||||||
|
}
|
||||||
|
if msgs := path.IsValidPathSegmentName(name); len(msgs) != 0 {
|
||||||
|
return "", apierrors.NewBadRequest(fmt.Sprintf("Name parameter invalid: %q: %s", name, strings.Join(msgs, ";")))
|
||||||
|
}
|
||||||
|
key := &Key{
|
||||||
|
Group: gr.Group,
|
||||||
|
Resource: gr.Resource,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
return prefix + key.String(), nil
|
||||||
|
}
|
@ -61,6 +61,8 @@ func NewStorage(
|
|||||||
s := &genericregistry.Store{
|
s := &genericregistry.Store{
|
||||||
NewFunc: resourceInfo.NewFunc,
|
NewFunc: resourceInfo.NewFunc,
|
||||||
NewListFunc: resourceInfo.NewListFunc,
|
NewListFunc: resourceInfo.NewListFunc,
|
||||||
|
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
|
||||||
|
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
|
||||||
PredicateFunc: grafanaregistry.Matcher,
|
PredicateFunc: grafanaregistry.Matcher,
|
||||||
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
||||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||||
|
@ -26,6 +26,8 @@ func newStorage(scheme *runtime.Scheme) (*storage, error) {
|
|||||||
store := &genericregistry.Store{
|
store := &genericregistry.Store{
|
||||||
NewFunc: resourceInfo.NewFunc,
|
NewFunc: resourceInfo.NewFunc,
|
||||||
NewListFunc: resourceInfo.NewListFunc,
|
NewListFunc: resourceInfo.NewListFunc,
|
||||||
|
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
|
||||||
|
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
|
||||||
PredicateFunc: grafanaregistry.Matcher,
|
PredicateFunc: grafanaregistry.Matcher,
|
||||||
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
||||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||||
|
@ -23,6 +23,8 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter, le
|
|||||||
store := &genericregistry.Store{
|
store := &genericregistry.Store{
|
||||||
NewFunc: resource.NewFunc,
|
NewFunc: resource.NewFunc,
|
||||||
NewListFunc: resource.NewListFunc,
|
NewListFunc: resource.NewListFunc,
|
||||||
|
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
|
||||||
|
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
|
||||||
PredicateFunc: grafanaregistry.Matcher,
|
PredicateFunc: grafanaregistry.Matcher,
|
||||||
DefaultQualifiedResource: resource.GroupResource(),
|
DefaultQualifiedResource: resource.GroupResource(),
|
||||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||||
|
@ -28,6 +28,8 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*
|
|||||||
store := &genericregistry.Store{
|
store := &genericregistry.Store{
|
||||||
NewFunc: resourceInfo.NewFunc,
|
NewFunc: resourceInfo.NewFunc,
|
||||||
NewListFunc: resourceInfo.NewListFunc,
|
NewListFunc: resourceInfo.NewListFunc,
|
||||||
|
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
|
||||||
|
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
|
||||||
PredicateFunc: grafanaregistry.Matcher,
|
PredicateFunc: grafanaregistry.Matcher,
|
||||||
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
||||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||||
|
@ -24,6 +24,8 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter, le
|
|||||||
store := &genericregistry.Store{
|
store := &genericregistry.Store{
|
||||||
NewFunc: resource.NewFunc,
|
NewFunc: resource.NewFunc,
|
||||||
NewListFunc: resource.NewListFunc,
|
NewListFunc: resource.NewListFunc,
|
||||||
|
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
|
||||||
|
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
|
||||||
PredicateFunc: grafanaregistry.Matcher,
|
PredicateFunc: grafanaregistry.Matcher,
|
||||||
DefaultQualifiedResource: resource.GroupResource(),
|
DefaultQualifiedResource: resource.GroupResource(),
|
||||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||||
|
@ -31,6 +31,8 @@ func newScopeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGette
|
|||||||
store := &genericregistry.Store{
|
store := &genericregistry.Store{
|
||||||
NewFunc: resourceInfo.NewFunc,
|
NewFunc: resourceInfo.NewFunc,
|
||||||
NewListFunc: resourceInfo.NewListFunc,
|
NewListFunc: resourceInfo.NewListFunc,
|
||||||
|
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
|
||||||
|
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
|
||||||
PredicateFunc: Matcher,
|
PredicateFunc: Matcher,
|
||||||
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
||||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||||
@ -73,6 +75,8 @@ func newScopeDashboardBindingStorage(scheme *runtime.Scheme, optsGetter generic.
|
|||||||
store := &genericregistry.Store{
|
store := &genericregistry.Store{
|
||||||
NewFunc: resourceInfo.NewFunc,
|
NewFunc: resourceInfo.NewFunc,
|
||||||
NewListFunc: resourceInfo.NewListFunc,
|
NewListFunc: resourceInfo.NewListFunc,
|
||||||
|
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
|
||||||
|
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
|
||||||
PredicateFunc: Matcher,
|
PredicateFunc: Matcher,
|
||||||
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
||||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||||
@ -115,6 +119,8 @@ func newScopeNodeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsG
|
|||||||
store := &genericregistry.Store{
|
store := &genericregistry.Store{
|
||||||
NewFunc: resourceInfo.NewFunc,
|
NewFunc: resourceInfo.NewFunc,
|
||||||
NewListFunc: resourceInfo.NewListFunc,
|
NewListFunc: resourceInfo.NewListFunc,
|
||||||
|
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
|
||||||
|
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
|
||||||
PredicateFunc: Matcher,
|
PredicateFunc: Matcher,
|
||||||
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
||||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||||
|
@ -28,6 +28,8 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*
|
|||||||
store := &genericregistry.Store{
|
store := &genericregistry.Store{
|
||||||
NewFunc: resourceInfo.NewFunc,
|
NewFunc: resourceInfo.NewFunc,
|
||||||
NewListFunc: resourceInfo.NewListFunc,
|
NewListFunc: resourceInfo.NewListFunc,
|
||||||
|
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()),
|
||||||
|
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()),
|
||||||
PredicateFunc: grafanaregistry.Matcher,
|
PredicateFunc: grafanaregistry.Matcher,
|
||||||
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
||||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||||
|
@ -23,12 +23,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/selection"
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
"k8s.io/apiserver/pkg/storage/storagebackend"
|
||||||
"k8s.io/apiserver/pkg/storage/storagebackend/factory"
|
"k8s.io/apiserver/pkg/storage/storagebackend/factory"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
entityStore "github.com/grafana/grafana/pkg/services/store/entity"
|
entityStore "github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,16 +74,16 @@ func NewStorage(
|
|||||||
// in seconds (0 means forever). If no error is returned and out is not nil, out will be
|
// in seconds (0 means forever). If no error is returned and out is not nil, out will be
|
||||||
// set to the read value from database.
|
// set to the read value from database.
|
||||||
func (s *Storage) Create(ctx context.Context, key string, obj runtime.Object, out runtime.Object, ttl uint64) error {
|
func (s *Storage) Create(ctx context.Context, key string, obj runtime.Object, out runtime.Object, ttl uint64) error {
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
k, err := grafanaregistry.ParseKey(key)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return apierrors.NewInternalError(fmt.Errorf("could not get request info"))
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Versioner().PrepareObjectForStorage(obj); err != nil {
|
if err := s.Versioner().PrepareObjectForStorage(obj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e, err := resourceToEntity(obj, requestInfo, s.codec)
|
e, err := resourceToEntity(obj, *k, s.codec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -114,17 +114,9 @@ func (s *Storage) Create(ctx context.Context, key string, obj runtime.Object, ou
|
|||||||
// current version of the object to avoid read operation from storage to get it.
|
// current version of the object to avoid read operation from storage to get it.
|
||||||
// However, the implementations have to retry in case suggestion is stale.
|
// However, the implementations have to retry in case suggestion is stale.
|
||||||
func (s *Storage) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc, cachedExistingObject runtime.Object) error {
|
func (s *Storage) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc, cachedExistingObject runtime.Object) error {
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
k, err := grafanaregistry.ParseKey(key)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return apierrors.NewInternalError(fmt.Errorf("could not get request info"))
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
k := &entityStore.Key{
|
|
||||||
Group: requestInfo.APIGroup,
|
|
||||||
Resource: requestInfo.Resource,
|
|
||||||
Namespace: requestInfo.Namespace,
|
|
||||||
Name: requestInfo.Name,
|
|
||||||
Subresource: requestInfo.Subresource,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previousVersion := int64(0)
|
previousVersion := int64(0)
|
||||||
@ -156,17 +148,9 @@ func (s *Storage) Delete(ctx context.Context, key string, out runtime.Object, pr
|
|||||||
// If resource version is "0", this interface will get current object at given key
|
// If resource version is "0", this interface will get current object at given key
|
||||||
// and send it in an "ADDED" event, before watch starts.
|
// and send it in an "ADDED" event, before watch starts.
|
||||||
func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) {
|
func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) {
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
k, err := grafanaregistry.ParseKey(key)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return nil, apierrors.NewInternalError(fmt.Errorf("could not get request info"))
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
k := &entityStore.Key{
|
|
||||||
Group: requestInfo.APIGroup,
|
|
||||||
Resource: requestInfo.Resource,
|
|
||||||
Namespace: requestInfo.Namespace,
|
|
||||||
Name: requestInfo.Name,
|
|
||||||
Subresource: requestInfo.Subresource,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Predicate.Field != nil {
|
if opts.Predicate.Field != nil {
|
||||||
@ -288,21 +272,12 @@ func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOption
|
|||||||
// The returned contents may be delayed, but it is guaranteed that they will
|
// The returned contents may be delayed, but it is guaranteed that they will
|
||||||
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
|
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
|
||||||
func (s *Storage) Get(ctx context.Context, key string, opts storage.GetOptions, objPtr runtime.Object) error {
|
func (s *Storage) Get(ctx context.Context, key string, opts storage.GetOptions, objPtr runtime.Object) error {
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
k, err := grafanaregistry.ParseKey(key)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return apierrors.NewInternalError(fmt.Errorf("could not get request info"))
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
k := &entityStore.Key{
|
|
||||||
Group: requestInfo.APIGroup,
|
|
||||||
Resource: requestInfo.Resource,
|
|
||||||
Namespace: requestInfo.Namespace,
|
|
||||||
Name: requestInfo.Name,
|
|
||||||
Subresource: requestInfo.Subresource,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceVersion := int64(0)
|
resourceVersion := int64(0)
|
||||||
var err error
|
|
||||||
if opts.ResourceVersion != "" {
|
if opts.ResourceVersion != "" {
|
||||||
resourceVersion, err = strconv.ParseInt(opts.ResourceVersion, 10, 64)
|
resourceVersion, err = strconv.ParseInt(opts.ResourceVersion, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -343,17 +318,9 @@ func (s *Storage) Get(ctx context.Context, key string, opts storage.GetOptions,
|
|||||||
// The returned contents may be delayed, but it is guaranteed that they will
|
// The returned contents may be delayed, but it is guaranteed that they will
|
||||||
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
|
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
|
||||||
func (s *Storage) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error {
|
func (s *Storage) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error {
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
k, err := grafanaregistry.ParseKey(key)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return apierrors.NewInternalError(fmt.Errorf("could not get request info"))
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
k := &entityStore.Key{
|
|
||||||
Group: requestInfo.APIGroup,
|
|
||||||
Resource: requestInfo.Resource,
|
|
||||||
Namespace: requestInfo.Namespace,
|
|
||||||
Name: requestInfo.Name,
|
|
||||||
Subresource: requestInfo.Subresource,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listPtr, err := meta.GetItemsPtr(listObj)
|
listPtr, err := meta.GetItemsPtr(listObj)
|
||||||
@ -519,17 +486,9 @@ func (s *Storage) GuaranteedUpdate(
|
|||||||
tryUpdate storage.UpdateFunc,
|
tryUpdate storage.UpdateFunc,
|
||||||
cachedExistingObject runtime.Object,
|
cachedExistingObject runtime.Object,
|
||||||
) error {
|
) error {
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
k, err := grafanaregistry.ParseKey(key)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return apierrors.NewInternalError(fmt.Errorf("could not get request info"))
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
k := &entityStore.Key{
|
|
||||||
Group: requestInfo.APIGroup,
|
|
||||||
Resource: requestInfo.Resource,
|
|
||||||
Namespace: requestInfo.Namespace,
|
|
||||||
Name: requestInfo.Name,
|
|
||||||
Subresource: requestInfo.Subresource,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getErr := s.Get(ctx, k.String(), storage.GetOptions{}, destination)
|
getErr := s.Get(ctx, k.String(), storage.GetOptions{}, destination)
|
||||||
@ -565,7 +524,7 @@ func (s *Storage) GuaranteedUpdate(
|
|||||||
return apierrors.NewInternalError(fmt.Errorf("could not successfully update object. key=%s, err=%s", k.String(), err.Error()))
|
return apierrors.NewInternalError(fmt.Errorf("could not successfully update object. key=%s, err=%s", k.String(), err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
e, err := resourceToEntity(updatedObj, requestInfo, s.codec)
|
e, err := resourceToEntity(updatedObj, *k, s.codec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,166 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/apiserver/pkg/storage"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ storage.Interface = &RequestInfoWrapper{}
|
|
||||||
|
|
||||||
type RequestInfoWrapper struct {
|
|
||||||
store storage.Interface
|
|
||||||
gr schema.GroupResource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) setRequestInfo(ctx context.Context, key string) (context.Context, error) {
|
|
||||||
pkey, err := convertToParsedKey(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = appcontext.WithUser(ctx, &user.SignedInUser{
|
|
||||||
Login: "admin",
|
|
||||||
UserID: 1,
|
|
||||||
OrgID: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
return request.WithRequestInfo(ctx, &request.RequestInfo{
|
|
||||||
APIGroup: pkey.Group,
|
|
||||||
APIVersion: "v1",
|
|
||||||
Resource: pkey.Resource,
|
|
||||||
Subresource: "",
|
|
||||||
Namespace: pkey.Namespace,
|
|
||||||
Name: pkey.Name,
|
|
||||||
Parts: strings.Split(key, "/"),
|
|
||||||
IsResourceRequest: true,
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) Create(ctx context.Context, key string, obj runtime.Object, out runtime.Object, ttl uint64) error {
|
|
||||||
ctx, err := r.setRequestInfo(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.store.Create(ctx, key, obj, out, ttl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc, cachedExistingObject runtime.Object) error {
|
|
||||||
ctx, err := r.setRequestInfo(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.store.Delete(ctx, key, out, preconditions, validateDeletion, cachedExistingObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) {
|
|
||||||
ctx, err := r.setRequestInfo(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.store.Watch(ctx, key, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) Get(ctx context.Context, key string, opts storage.GetOptions, objPtr runtime.Object) error {
|
|
||||||
ctx, err := r.setRequestInfo(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.store.Get(ctx, key, opts, objPtr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error {
|
|
||||||
ctx, err := r.setRequestInfo(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.store.GetList(ctx, key, opts, listObj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) GuaranteedUpdate(ctx context.Context, key string, destination runtime.Object, ignoreNotFound bool, preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, cachedExistingObject runtime.Object) error {
|
|
||||||
ctx, err := r.setRequestInfo(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.store.GuaranteedUpdate(ctx, key, destination, ignoreNotFound, preconditions, tryUpdate, cachedExistingObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) Count(key string) (int64, error) {
|
|
||||||
return r.store.Count(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) Versioner() storage.Versioner {
|
|
||||||
return r.store.Versioner()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RequestInfoWrapper) RequestWatchProgress(ctx context.Context) error {
|
|
||||||
return r.store.RequestWatchProgress(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Key struct {
|
|
||||||
Group string
|
|
||||||
Resource string
|
|
||||||
Namespace string
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToParsedKey(key string) (*Key, error) {
|
|
||||||
// NOTE: the following supports the watcher tests that run against v1/pods
|
|
||||||
// Other than that, there are ambiguities in the key format that only field selector
|
|
||||||
// when set to use metadata.name can be used to bring clarity in the 3-segment case
|
|
||||||
|
|
||||||
// Cases handled below:
|
|
||||||
// namespace scoped:
|
|
||||||
// /<resource>/[<namespace>]/[<name>]
|
|
||||||
// /<resource>/[<namespace>]
|
|
||||||
//
|
|
||||||
// cluster scoped:
|
|
||||||
// /<resource>/[<name>]
|
|
||||||
// /<resource>
|
|
||||||
k := &Key{}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(key, "/") {
|
|
||||||
key = "/" + key
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.SplitN(key, "/", 5)
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return nil, fmt.Errorf("invalid key format: %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
k.Resource = parts[1]
|
|
||||||
if len(parts) < 3 {
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// figure out whether the key is namespace scoped or cluster scoped
|
|
||||||
if isTestNs(parts[2]) {
|
|
||||||
k.Namespace = parts[2]
|
|
||||||
if len(parts) >= 4 {
|
|
||||||
k.Name = parts[3]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
k.Name = parts[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isTestNs(part string) bool {
|
|
||||||
return strings.HasPrefix(part, "test-ns-") || strings.HasPrefix(part, "ns-") || strings.Index(part, "-ns") > 0
|
|
||||||
}
|
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"k8s.io/apimachinery/pkg/api/apitesting"
|
"k8s.io/apimachinery/pkg/api/apitesting"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -20,11 +21,13 @@ import (
|
|||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apiserver/pkg/apis/example"
|
"k8s.io/apiserver/pkg/apis/example"
|
||||||
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
||||||
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
"k8s.io/apiserver/pkg/storage/storagebackend"
|
||||||
"k8s.io/apiserver/pkg/storage/storagebackend/factory"
|
"k8s.io/apiserver/pkg/storage/storagebackend/factory"
|
||||||
storagetesting "k8s.io/apiserver/pkg/storage/testing"
|
storagetesting "k8s.io/apiserver/pkg/storage/testing"
|
||||||
|
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/storage/entity"
|
"github.com/grafana/grafana/pkg/services/apiserver/storage/entity"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
@ -129,7 +132,12 @@ func testSetup(t *testing.T, opts ...setupOption) (context.Context, storage.Inte
|
|||||||
client,
|
client,
|
||||||
setupOpts.codec,
|
setupOpts.codec,
|
||||||
func(obj runtime.Object) (string, error) {
|
func(obj runtime.Object) (string, error) {
|
||||||
return storage.NamespaceKeyFunc(setupOpts.resourcePrefix, obj)
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
keyFn := grafanaregistry.NamespaceKeyFunc(setupOpts.groupResource)
|
||||||
|
return keyFn(genericapirequest.WithNamespace(genericapirequest.NewContext(), accessor.GetNamespace()), accessor.GetName())
|
||||||
},
|
},
|
||||||
setupOpts.newFunc,
|
setupOpts.newFunc,
|
||||||
setupOpts.newListFunc,
|
setupOpts.newListFunc,
|
||||||
@ -141,12 +149,7 @@ func testSetup(t *testing.T, opts ...setupOption) (context.Context, storage.Inte
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
wrappedStore := &RequestInfoWrapper{
|
return ctx, store, destroyFunc, nil
|
||||||
store: store,
|
|
||||||
gr: setupOpts.groupResource,
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx, wrappedStore, destroyFunc, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationWatch(t *testing.T) {
|
func TestIntegrationWatch(t *testing.T) {
|
||||||
@ -250,6 +253,7 @@ func TestIntegrationWatchContextCancel(t *testing.T) {
|
|||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping integration test")
|
t.Skip("skipping integration test")
|
||||||
}
|
}
|
||||||
|
t.Skip("In maintenance")
|
||||||
|
|
||||||
ctx, store, destroyFunc, err := testSetup(t)
|
ctx, store, destroyFunc, err := testSetup(t)
|
||||||
defer destroyFunc()
|
defer destroyFunc()
|
||||||
@ -323,6 +327,7 @@ func TestIntegrationSendInitialEventsBackwardCompatibility(t *testing.T) {
|
|||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping integration test")
|
t.Skip("skipping integration test")
|
||||||
}
|
}
|
||||||
|
t.Skip("In maintenance")
|
||||||
|
|
||||||
ctx, store, destroyFunc, err := testSetup(t)
|
ctx, store, destroyFunc, err := testSetup(t)
|
||||||
defer destroyFunc()
|
defer destroyFunc()
|
||||||
|
@ -13,9 +13,9 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
entityStore "github.com/grafana/grafana/pkg/services/store/entity"
|
entityStore "github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ func EntityToRuntimeObject(rsp *entityStore.Entity, res runtime.Object, codec ru
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceToEntity(res runtime.Object, requestInfo *request.RequestInfo, codec runtime.Codec) (*entityStore.Entity, error) {
|
func resourceToEntity(res runtime.Object, k grafanaregistry.Key, codec runtime.Codec) (*entityStore.Entity, error) {
|
||||||
metaAccessor, err := meta.Accessor(res)
|
metaAccessor, err := meta.Accessor(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -110,19 +110,13 @@ func resourceToEntity(res runtime.Object, requestInfo *request.RequestInfo, code
|
|||||||
}
|
}
|
||||||
rv, _ := strconv.ParseInt(metaAccessor.GetResourceVersion(), 10, 64)
|
rv, _ := strconv.ParseInt(metaAccessor.GetResourceVersion(), 10, 64)
|
||||||
|
|
||||||
k := &entityStore.Key{
|
// add the object's name to the provided key
|
||||||
Group: requestInfo.APIGroup,
|
k.Name = metaAccessor.GetName()
|
||||||
Resource: requestInfo.Resource,
|
|
||||||
Namespace: requestInfo.Namespace,
|
|
||||||
Name: metaAccessor.GetName(),
|
|
||||||
Subresource: requestInfo.Subresource,
|
|
||||||
}
|
|
||||||
|
|
||||||
rsp := &entityStore.Entity{
|
rsp := &entityStore.Entity{
|
||||||
Group: k.Group,
|
Group: k.Group,
|
||||||
GroupVersion: requestInfo.APIVersion,
|
GroupVersion: res.GetObjectKind().GroupVersionKind().Version,
|
||||||
Resource: k.Resource,
|
Resource: k.Resource,
|
||||||
Subresource: k.Subresource,
|
|
||||||
Namespace: k.Namespace,
|
Namespace: k.Namespace,
|
||||||
Key: k.String(),
|
Key: k.String(),
|
||||||
Name: k.Name,
|
Name: k.Name,
|
||||||
|
@ -10,9 +10,9 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
|
"github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
entityStore "github.com/grafana/grafana/pkg/services/store/entity"
|
entityStore "github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ func TestResourceToEntity(t *testing.T) {
|
|||||||
Codecs := serializer.NewCodecFactory(Scheme)
|
Codecs := serializer.NewCodecFactory(Scheme)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
requestInfo *request.RequestInfo
|
key grafanaregistry.Key
|
||||||
resource runtime.Object
|
resource runtime.Object
|
||||||
codec runtime.Codec
|
codec runtime.Codec
|
||||||
expectedKey string
|
expectedKey string
|
||||||
@ -52,14 +52,17 @@ func TestResourceToEntity(t *testing.T) {
|
|||||||
expectedBody []byte
|
expectedBody []byte
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
requestInfo: &request.RequestInfo{
|
key: grafanaregistry.Key{
|
||||||
APIGroup: "playlist.grafana.app",
|
Group: "playlist.grafana.app",
|
||||||
APIVersion: "v0alpha1",
|
Resource: "playlists",
|
||||||
Resource: "playlists",
|
Namespace: "default",
|
||||||
Namespace: "default",
|
Name: "test-name",
|
||||||
Name: "test-name",
|
|
||||||
},
|
},
|
||||||
resource: &v0alpha1.Playlist{
|
resource: &v0alpha1.Playlist{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "playlist.grafana.app/v0alpha1",
|
||||||
|
Kind: "Playlist",
|
||||||
|
},
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
CreationTimestamp: createdAt,
|
CreationTimestamp: createdAt,
|
||||||
Labels: map[string]string{"label1": "value1", "label2": "value2"},
|
Labels: map[string]string{"label1": "value1", "label2": "value2"},
|
||||||
@ -105,7 +108,7 @@ func TestResourceToEntity(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.resource.GetObjectKind().GroupVersionKind().Kind+" to entity conversion should succeed", func(t *testing.T) {
|
t.Run(tc.resource.GetObjectKind().GroupVersionKind().Kind+" to entity conversion should succeed", func(t *testing.T) {
|
||||||
entity, err := resourceToEntity(tc.resource, tc.requestInfo, Codecs.LegacyCodec(v0alpha1.PlaylistResourceInfo.GroupVersion()))
|
entity, err := resourceToEntity(tc.resource, tc.key, Codecs.LegacyCodec(v0alpha1.PlaylistResourceInfo.GroupVersion()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, tc.expectedKey, entity.Key)
|
assert.Equal(t, tc.expectedKey, entity.Key)
|
||||||
assert.Equal(t, tc.expectedName, entity.Name)
|
assert.Equal(t, tc.expectedName, entity.Name)
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
package entity
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Key struct {
|
|
||||||
Group string
|
|
||||||
Resource string
|
|
||||||
Namespace string
|
|
||||||
Name string
|
|
||||||
Subresource string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseKey(key string) (*Key, error) {
|
|
||||||
// /<group>/<resource>[/namespaces/<namespace>][/<name>[/<subresource>]]
|
|
||||||
parts := strings.Split(key, "/")
|
|
||||||
if len(parts) < 3 {
|
|
||||||
return nil, fmt.Errorf("invalid key (expecting at least 2 parts): %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parts[0] != "" {
|
|
||||||
return nil, fmt.Errorf("invalid key (expecting leading slash): %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
k := &Key{
|
|
||||||
Group: parts[1],
|
|
||||||
Resource: parts[2],
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) == 3 {
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if parts[3] != "namespaces" {
|
|
||||||
k.Name = parts[3]
|
|
||||||
if len(parts) > 4 {
|
|
||||||
k.Subresource = strings.Join(parts[4:], "/")
|
|
||||||
}
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) < 5 {
|
|
||||||
return nil, fmt.Errorf("invalid key (expecting namespace after 'namespaces'): %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
k.Namespace = parts[4]
|
|
||||||
|
|
||||||
if len(parts) == 5 {
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
k.Name = parts[5]
|
|
||||||
if len(parts) > 6 {
|
|
||||||
k.Subresource = strings.Join(parts[6:], "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *Key) String() string {
|
|
||||||
s := "/" + k.Group + "/" + k.Resource
|
|
||||||
if len(k.Namespace) > 0 {
|
|
||||||
s += "/namespaces/" + k.Namespace
|
|
||||||
}
|
|
||||||
if len(k.Name) > 0 {
|
|
||||||
s += "/" + k.Name
|
|
||||||
if len(k.Subresource) > 0 {
|
|
||||||
s += "/" + k.Subresource
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *Key) IsEqual(other *Key) bool {
|
|
||||||
return k.Group == other.Group &&
|
|
||||||
k.Resource == other.Resource &&
|
|
||||||
k.Namespace == other.Namespace &&
|
|
||||||
k.Name == other.Name &&
|
|
||||||
k.Subresource == other.Subresource
|
|
||||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
||||||
@ -21,7 +22,7 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := entity.ParseKey(r.Entity.Key)
|
key, err := grafanaregistry.ParseKey(r.Entity.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create entity: parse entity key: %w", err)
|
return nil, fmt.Errorf("create entity: parse entity key: %w", err)
|
||||||
}
|
}
|
||||||
@ -98,7 +99,7 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ
|
|||||||
|
|
||||||
// entityForCreate validates the given request and returns a *returnsEntity
|
// entityForCreate validates the given request and returns a *returnsEntity
|
||||||
// populated accordingly.
|
// populated accordingly.
|
||||||
func entityForCreate(ctx context.Context, r *entity.CreateEntityRequest, key *entity.Key) (*returnsEntity, error) {
|
func entityForCreate(ctx context.Context, r *entity.CreateEntityRequest, key *grafanaregistry.Key) (*returnsEntity, error) {
|
||||||
newEntity := &returnsEntity{
|
newEntity := &returnsEntity{
|
||||||
Entity: cloneEntity(r.Entity),
|
Entity: cloneEntity(r.Entity),
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
||||||
@ -20,7 +21,7 @@ func (s *sqlEntityServer) Delete(ctx context.Context, r *entity.DeleteEntityRequ
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := entity.ParseKey(r.Key)
|
key, err := grafanaregistry.ParseKey(r.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("delete entity: parse entity key: %w", err)
|
return nil, fmt.Errorf("delete entity: parse entity key: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
||||||
@ -218,7 +219,7 @@ func (r sqlEntityListFolderElementsRequest) Validate() error {
|
|||||||
// cases and proper database deserialization.
|
// cases and proper database deserialization.
|
||||||
type sqlEntityReadRequest struct {
|
type sqlEntityReadRequest struct {
|
||||||
*sqltemplate.SQLTemplate
|
*sqltemplate.SQLTemplate
|
||||||
Key *entity.Key
|
Key *grafanaregistry.Key
|
||||||
ResourceVersion int64
|
ResourceVersion int64
|
||||||
SelectForUpdate bool
|
SelectForUpdate bool
|
||||||
returnsEntitySet
|
returnsEntitySet
|
||||||
@ -230,7 +231,7 @@ func (r sqlEntityReadRequest) Validate() error {
|
|||||||
|
|
||||||
type sqlEntityDeleteRequest struct {
|
type sqlEntityDeleteRequest struct {
|
||||||
*sqltemplate.SQLTemplate
|
*sqltemplate.SQLTemplate
|
||||||
Key *entity.Key
|
Key *grafanaregistry.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sqlEntityDeleteRequest) Validate() error {
|
func (r sqlEntityDeleteRequest) Validate() error {
|
||||||
@ -479,7 +480,7 @@ func readEntity(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
x db.ContextExecer,
|
x db.ContextExecer,
|
||||||
d sqltemplate.Dialect,
|
d sqltemplate.Dialect,
|
||||||
k *entity.Key,
|
k *grafanaregistry.Key,
|
||||||
asOfVersion int64,
|
asOfVersion int64,
|
||||||
optimisticLocking bool,
|
optimisticLocking bool,
|
||||||
selectForUpdate bool,
|
selectForUpdate bool,
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
sqlmock "github.com/DATA-DOG/go-sqlmock"
|
sqlmock "github.com/DATA-DOG/go-sqlmock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
||||||
@ -107,7 +108,7 @@ func TestQueries(t *testing.T) {
|
|||||||
Name: "single path",
|
Name: "single path",
|
||||||
Data: &sqlEntityDeleteRequest{
|
Data: &sqlEntityDeleteRequest{
|
||||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||||
Key: new(entity.Key),
|
Key: new(grafanaregistry.Key),
|
||||||
},
|
},
|
||||||
Expected: expected{
|
Expected: expected{
|
||||||
"entity_delete_mysql_sqlite.sql": dialects{
|
"entity_delete_mysql_sqlite.sql": dialects{
|
||||||
@ -173,7 +174,7 @@ func TestQueries(t *testing.T) {
|
|||||||
Name: "with resource version and select for update",
|
Name: "with resource version and select for update",
|
||||||
Data: &sqlEntityReadRequest{
|
Data: &sqlEntityReadRequest{
|
||||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||||
Key: new(entity.Key),
|
Key: new(grafanaregistry.Key),
|
||||||
ResourceVersion: 1,
|
ResourceVersion: 1,
|
||||||
SelectForUpdate: true,
|
SelectForUpdate: true,
|
||||||
returnsEntitySet: returnsEntitySet{
|
returnsEntitySet: returnsEntitySet{
|
||||||
@ -190,7 +191,7 @@ func TestQueries(t *testing.T) {
|
|||||||
Name: "without resource version and select for update",
|
Name: "without resource version and select for update",
|
||||||
Data: &sqlEntityReadRequest{
|
Data: &sqlEntityReadRequest{
|
||||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||||
Key: new(entity.Key),
|
Key: new(grafanaregistry.Key),
|
||||||
returnsEntitySet: returnsEntitySet{
|
returnsEntitySet: returnsEntitySet{
|
||||||
Entity: newReturnsEntity(),
|
Entity: newReturnsEntity(),
|
||||||
},
|
},
|
||||||
@ -547,7 +548,7 @@ func TestReadEntity(t *testing.T) {
|
|||||||
// readonly, shared data for all subtests
|
// readonly, shared data for all subtests
|
||||||
expectedEntity := newEmptyEntity()
|
expectedEntity := newEmptyEntity()
|
||||||
testdataJSON(t, `grpc-res-entity.json`, expectedEntity)
|
testdataJSON(t, `grpc-res-entity.json`, expectedEntity)
|
||||||
key, err := entity.ParseKey(expectedEntity.Key)
|
key, err := grafanaregistry.ParseKey(expectedEntity.Key)
|
||||||
require.NoErrorf(t, err, "provided key: %#v", expectedEntity)
|
require.NoErrorf(t, err, "provided key: %#v", expectedEntity)
|
||||||
|
|
||||||
t.Run("happy path - entity table, optimistic locking", func(t *testing.T) {
|
t.Run("happy path - entity table, optimistic locking", func(t *testing.T) {
|
||||||
@ -567,7 +568,7 @@ func TestReadEntity(t *testing.T) {
|
|||||||
db, mock := newMockDBMatchWords(t)
|
db, mock := newMockDBMatchWords(t)
|
||||||
readReq := sqlEntityReadRequest{ // used to generate mock results
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||||
Key: new(entity.Key),
|
Key: new(grafanaregistry.Key),
|
||||||
returnsEntitySet: newReturnsEntitySet(),
|
returnsEntitySet: newReturnsEntitySet(),
|
||||||
}
|
}
|
||||||
readReq.Entity.Entity = cloneEntity(expectedEntity)
|
readReq.Entity.Entity = cloneEntity(expectedEntity)
|
||||||
@ -592,7 +593,7 @@ func TestReadEntity(t *testing.T) {
|
|||||||
db, mock := newMockDBMatchWords(t)
|
db, mock := newMockDBMatchWords(t)
|
||||||
readReq := sqlEntityReadRequest{ // used to generate mock results
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||||
Key: new(entity.Key),
|
Key: new(grafanaregistry.Key),
|
||||||
returnsEntitySet: newReturnsEntitySet(),
|
returnsEntitySet: newReturnsEntitySet(),
|
||||||
}
|
}
|
||||||
readReq.Entity.Entity = cloneEntity(expectedEntity)
|
readReq.Entity.Entity = cloneEntity(expectedEntity)
|
||||||
@ -627,7 +628,7 @@ func TestReadEntity(t *testing.T) {
|
|||||||
db, mock := newMockDBMatchWords(t)
|
db, mock := newMockDBMatchWords(t)
|
||||||
readReq := sqlEntityReadRequest{ // used to generate mock results
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||||
Key: new(entity.Key),
|
Key: new(grafanaregistry.Key),
|
||||||
returnsEntitySet: newReturnsEntitySet(),
|
returnsEntitySet: newReturnsEntitySet(),
|
||||||
}
|
}
|
||||||
results := newMockResults(t, mock, sqlEntityRead, readReq)
|
results := newMockResults(t, mock, sqlEntityRead, readReq)
|
||||||
@ -652,7 +653,7 @@ func TestReadEntity(t *testing.T) {
|
|||||||
db, mock := newMockDBMatchWords(t)
|
db, mock := newMockDBMatchWords(t)
|
||||||
readReq := sqlEntityReadRequest{ // used to generate mock results
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||||
Key: new(entity.Key),
|
Key: new(grafanaregistry.Key),
|
||||||
returnsEntitySet: newReturnsEntitySet(),
|
returnsEntitySet: newReturnsEntitySet(),
|
||||||
}
|
}
|
||||||
readReq.Entity.Entity = cloneEntity(expectedEntity)
|
readReq.Entity.Entity = cloneEntity(expectedEntity)
|
||||||
@ -684,7 +685,7 @@ func expectReadEntity(t *testing.T, mock sqlmock.Sqlmock, e *entity.Entity) func
|
|||||||
// test declarations
|
// test declarations
|
||||||
readReq := sqlEntityReadRequest{ // used to generate mock results
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||||
Key: new(entity.Key),
|
Key: new(grafanaregistry.Key),
|
||||||
returnsEntitySet: newReturnsEntitySet(),
|
returnsEntitySet: newReturnsEntitySet(),
|
||||||
}
|
}
|
||||||
results := newMockResults(t, mock, sqlEntityRead, readReq)
|
results := newMockResults(t, mock, sqlEntityRead, readReq)
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||||
@ -302,7 +303,7 @@ func (s *sqlEntityServer) read(ctx context.Context, tx session.SessionQuerier, r
|
|||||||
return nil, fmt.Errorf("missing key")
|
return nil, fmt.Errorf("missing key")
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := entity.ParseKey(r.Key)
|
key, err := grafanaregistry.ParseKey(r.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -395,7 +396,7 @@ func (s *sqlEntityServer) history(ctx context.Context, r *entity.EntityHistoryRe
|
|||||||
entityQuery.AddFields(fields...)
|
entityQuery.AddFields(fields...)
|
||||||
|
|
||||||
if r.Key != "" {
|
if r.Key != "" {
|
||||||
key, err := entity.ParseKey(r.Key)
|
key, err := grafanaregistry.ParseKey(r.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -629,7 +630,7 @@ func (s *sqlEntityServer) List(ctx context.Context, r *entity.EntityListRequest)
|
|||||||
where := []string{}
|
where := []string{}
|
||||||
args := []any{}
|
args := []any{}
|
||||||
for _, k := range r.Key {
|
for _, k := range r.Key {
|
||||||
key, err := entity.ParseKey(k)
|
key, err := grafanaregistry.ParseKey(k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -868,7 +869,7 @@ func (s *sqlEntityServer) watchInit(ctx context.Context, r *entity.EntityWatchRe
|
|||||||
where := []string{}
|
where := []string{}
|
||||||
args := []any{}
|
args := []any{}
|
||||||
for _, k := range r.Key {
|
for _, k := range r.Key {
|
||||||
key, err := entity.ParseKey(k)
|
key, err := grafanaregistry.ParseKey(k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctxLogger.Error("error parsing key", "error", err, "key", k)
|
ctxLogger.Error("error parsing key", "error", err, "key", k)
|
||||||
return lastRv, err
|
return lastRv, err
|
||||||
@ -1153,7 +1154,7 @@ func watchMatches(r *entity.EntityWatchRequest, result *entity.Entity) bool {
|
|||||||
if len(r.Key) > 0 {
|
if len(r.Key) > 0 {
|
||||||
matched := false
|
matched := false
|
||||||
for _, k := range r.Key {
|
for _, k := range r.Key {
|
||||||
key, err := entity.ParseKey(k)
|
key, err := grafanaregistry.ParseKey(k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
||||||
@ -21,7 +22,7 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := entity.ParseKey(r.Entity.Key)
|
key, err := grafanaregistry.ParseKey(r.Entity.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("update entity: parse entity key: %w", err)
|
return nil, fmt.Errorf("update entity: parse entity key: %w", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user