mirror of https://github.com/grafana/grafana
Storage: Avoid relying on RequestInfo (#89635)
parent
4cf3ebbb3d
commit
71270f3203
@ -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 |
||||||
|
} |
@ -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 |
|
||||||
} |
|
@ -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 |
|
||||||
} |
|
Loading…
Reference in new issue