refactor(unified-storage): move generated protos to own pkg (#105356)

This commit is contained in:
Jean-Philippe Quéméner
2025-05-15 21:36:52 +02:00
committed by GitHub
parent aa2cf8e398
commit 002f46736a
150 changed files with 4284 additions and 3863 deletions
+3 -3
View File
@@ -7,7 +7,7 @@ import (
dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type LargeObjectSupportFake struct {
@@ -28,12 +28,12 @@ func (s *LargeObjectSupportFake) MaxSize() int {
return 10 * 1024 * 1024
}
func (s *LargeObjectSupportFake) Deconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error {
func (s *LargeObjectSupportFake) Deconstruct(_ context.Context, _ *resourcepb.ResourceKey, _ resourcepb.BlobStoreClient, _ utils.GrafanaMetaAccessor, _ []byte) error {
s.deconstructed = true
return nil
}
func (s *LargeObjectSupportFake) Reconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor) error {
func (s *LargeObjectSupportFake) Reconstruct(_ context.Context, _ *resourcepb.ResourceKey, _ resourcepb.BlobStoreClient, _ utils.GrafanaMetaAccessor) error {
s.reconstructed = true
return nil
}
+1 -1
View File
@@ -20,7 +20,7 @@ require (
github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20250422074709-7c8433fbb2c2
github.com/stretchr/testify v1.10.0
gocloud.dev v0.40.0
google.golang.org/grpc v1.72.0
google.golang.org/grpc v1.72.1
k8s.io/apimachinery v0.32.3
k8s.io/apiserver v0.32.3
k8s.io/client-go v0.32.3
+1 -2
View File
@@ -869,8 +869,7 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+8 -8
View File
@@ -10,7 +10,7 @@ import (
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type LargeObjectSupport interface {
@@ -27,11 +27,11 @@ type LargeObjectSupport interface {
// Deconstruct takes a large object, write most of it to blob storage and leave a few metadata bits around to help with list
// NOTE: changes to the object must be handled by mutating the input obj
Deconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error
Deconstruct(ctx context.Context, key *resourcepb.ResourceKey, client resourcepb.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error
// Reconstruct will join the resource+blob back into a complete resource
// NOTE: changes to the object must be handled by mutating the input obj
Reconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor) error
Reconstruct(ctx context.Context, key *resourcepb.ResourceKey, client resourcepb.BlobStoreClient, obj utils.GrafanaMetaAccessor) error
}
var _ LargeObjectSupport = (*BasicLargeObjectSupport)(nil)
@@ -64,7 +64,7 @@ func (s *BasicLargeObjectSupport) MaxSize() int {
}
// Deconstruct implements LargeObjectSupport.
func (s *BasicLargeObjectSupport) Deconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error {
func (s *BasicLargeObjectSupport) Deconstruct(ctx context.Context, key *resourcepb.ResourceKey, client resourcepb.BlobStoreClient, obj utils.GrafanaMetaAccessor, raw []byte) error {
if key.Group != s.TheGroupResource.Group {
return fmt.Errorf("requested group mismatch")
}
@@ -104,7 +104,7 @@ func (s *BasicLargeObjectSupport) Deconstruct(ctx context.Context, key *resource
}
// Save the blob
info, err := client.PutBlob(ctx, &resource.PutBlobRequest{
info, err := client.PutBlob(ctx, &resourcepb.PutBlobRequest{
ContentType: "application/json",
Value: val,
Resource: key,
@@ -125,7 +125,7 @@ func (s *BasicLargeObjectSupport) Deconstruct(ctx context.Context, key *resource
}
// Reconstruct implements LargeObjectSupport.
func (s *BasicLargeObjectSupport) Reconstruct(ctx context.Context, key *resource.ResourceKey, client resource.BlobStoreClient, obj utils.GrafanaMetaAccessor) error {
func (s *BasicLargeObjectSupport) Reconstruct(ctx context.Context, key *resourcepb.ResourceKey, client resourcepb.BlobStoreClient, obj utils.GrafanaMetaAccessor) error {
blobInfo := obj.GetBlob()
if blobInfo == nil {
return fmt.Errorf("the object does not have a blob")
@@ -135,8 +135,8 @@ func (s *BasicLargeObjectSupport) Reconstruct(ctx context.Context, key *resource
if err != nil {
return err
}
rsp, err := client.GetBlob(ctx, &resource.GetBlobRequest{
Resource: &resource.ResourceKey{
rsp, err := client.GetBlob(ctx, &resourcepb.GetBlobRequest{
Resource: &resourcepb.ResourceKey{
Group: s.TheGroupResource.Group,
Resource: s.TheGroupResource.Resource,
Namespace: obj.GetNamespace(),
+6 -5
View File
@@ -16,8 +16,9 @@ import (
"k8s.io/client-go/rest"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var errResourceIsManagedInRepository = fmt.Errorf("this resource is managed by a repository")
@@ -97,7 +98,7 @@ func enforceManagerProperties(auth authtypes.AuthInfo, obj utils.GrafanaMetaAcce
func (s *Storage) handleManagedResourceRouting(ctx context.Context,
err error,
action resource.WatchEvent_Type,
action resourcepb.WatchEvent_Type,
key string,
orig runtime.Object,
rsp runtime.Object,
@@ -137,7 +138,7 @@ func (s *Storage) handleManagedResourceRouting(ctx context.Context,
return err
}
if action == resource.WatchEvent_DELETED {
if action == resourcepb.WatchEvent_DELETED {
// TODO? can we copy orig into rsp without a full get?
if err = s.Get(ctx, key, storage.GetOptions{}, rsp); err != nil { // COPY?
return err
@@ -153,9 +154,9 @@ func (s *Storage) handleManagedResourceRouting(ctx context.Context,
var req *rest.Request
switch action {
case resource.WatchEvent_ADDED:
case resourcepb.WatchEvent_ADDED:
req = client.Post()
case resource.WatchEvent_MODIFIED:
case resourcepb.WatchEvent_MODIFIED:
req = client.Put()
default:
return fmt.Errorf("unsupported provisioning action: %v, %w", action, err)
+3 -2
View File
@@ -8,14 +8,15 @@ import (
"k8s.io/apimachinery/pkg/runtime"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type permissionCreatorFunc = func(ctx context.Context) error
func afterCreatePermissionCreator(ctx context.Context,
key *resource.ResourceKey,
key *resourcepb.ResourceKey,
grantPermisions string,
obj runtime.Object,
setter DefaultPermissionSetter,
@@ -8,14 +8,15 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestAfterCreatePermissionCreator(t *testing.T) {
mockSetter := func(ctx context.Context, key *resource.ResourceKey, auth authtypes.AuthInfo, val utils.GrafanaMetaAccessor) error {
mockSetter := func(ctx context.Context, key *resourcepb.ResourceKey, auth authtypes.AuthInfo, val utils.GrafanaMetaAccessor) error {
return nil
}
@@ -69,7 +70,7 @@ func TestAfterCreatePermissionCreator(t *testing.T) {
UserID: 1,
})
obj := &v0alpha1.Dashboard{}
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "test",
@@ -92,7 +93,7 @@ func TestAfterCreatePermissionCreator(t *testing.T) {
UserID: 1,
})
obj := &v0alpha1.Dashboard{}
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "test",
+3 -2
View File
@@ -17,8 +17,9 @@ import (
"k8s.io/klog/v2"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func logN(n, b float64) float64 {
@@ -197,7 +198,7 @@ func (s *Storage) handleLargeResources(ctx context.Context, obj utils.GrafanaMet
return nil, fmt.Errorf("request object is too big (%s > %s)", formatBytes(size), formatBytes(support.MaxSize()))
}
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: s.gr.Group,
Resource: s.gr.Resource,
Namespace: obj.GetNamespace(),
+16 -14
View File
@@ -32,10 +32,12 @@ import (
"k8s.io/client-go/tools/cache"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
"github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
const (
@@ -46,7 +48,7 @@ const (
var _ storage.Interface = (*Storage)(nil)
type DefaultPermissionSetter = func(ctx context.Context, key *resource.ResourceKey, id authtypes.AuthInfo, obj utils.GrafanaMetaAccessor) error
type DefaultPermissionSetter = func(ctx context.Context, key *resourcepb.ResourceKey, id authtypes.AuthInfo, obj utils.GrafanaMetaAccessor) error
// Optional settings that apply to a single resource
type StorageOptions struct {
@@ -76,7 +78,7 @@ type Storage struct {
indexers *cache.Indexers
store resource.ResourceClient
getKey func(string) (*resource.ResourceKey, error)
getKey func(string) (*resourcepb.ResourceKey, error)
snowflake *snowflake.Node // used to enforce internal ids
configProvider RestConfigProvider // used for provisioning
@@ -101,7 +103,7 @@ func NewStorage(
config *storagebackend.ConfigForResource,
store resource.ResourceClient,
keyFunc func(obj runtime.Object) (string, error),
keyParser func(key string) (*resource.ResourceKey, error),
keyParser func(key string) (*resourcepb.ResourceKey, error),
newFunc func() runtime.Object,
newListFunc func() runtime.Object,
getAttrsFunc storage.AttrFunc,
@@ -139,7 +141,7 @@ func NewStorage(
// The key parsing callback allows us to support the hardcoded paths from upstream tests
if s.getKey == nil {
s.getKey = func(key string) (*resource.ResourceKey, error) {
s.getKey = func(key string) (*resourcepb.ResourceKey, error) {
k, err := grafanaregistry.ParseKey(key)
if err != nil {
return nil, err
@@ -150,7 +152,7 @@ func NewStorage(
if k.Resource == "" {
return nil, apierrors.NewInternalError(fmt.Errorf("missing resource in request"))
}
return &resource.ResourceKey{
return &resourcepb.ResourceKey{
Namespace: k.Namespace,
Group: k.Group,
Resource: k.Resource,
@@ -177,10 +179,10 @@ func (s *Storage) convertToObject(data []byte, obj runtime.Object) (runtime.Obje
func (s *Storage) Create(ctx context.Context, key string, obj runtime.Object, out runtime.Object, ttl uint64) error {
var err error
var permissions string
req := &resource.CreateRequest{}
req := &resourcepb.CreateRequest{}
req.Value, permissions, err = s.prepareObjectForStorage(ctx, obj)
if err != nil {
return s.handleManagedResourceRouting(ctx, err, resource.WatchEvent_ADDED, key, obj, out)
return s.handleManagedResourceRouting(ctx, err, resourcepb.WatchEvent_ADDED, key, obj, out)
}
req.Key, err = s.getKey(key)
@@ -258,7 +260,7 @@ func (s *Storage) Delete(
if err != nil {
return err
}
cmd := &resource.DeleteRequest{Key: k}
cmd := &resourcepb.DeleteRequest{Key: k}
if preconditions != nil {
if err := preconditions.Check(key, out); err != nil {
@@ -287,7 +289,7 @@ func (s *Storage) Delete(
return fmt.Errorf("unable to read object %w", err)
}
if err = checkManagerPropertiesOnDelete(info, meta); err != nil {
return s.handleManagedResourceRouting(ctx, err, resource.WatchEvent_DELETED, key, out, out)
return s.handleManagedResourceRouting(ctx, err, resourcepb.WatchEvent_DELETED, key, out, out)
}
rsp, err := s.store.Delete(ctx, cmd)
@@ -315,7 +317,7 @@ func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOption
return watch.NewEmptyWatch(), nil
}
cmd := &resource.WatchRequest{
cmd := &resourcepb.WatchRequest{
Since: req.ResourceVersion,
Options: req.Options,
SendInitialEvents: false,
@@ -349,7 +351,7 @@ func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOption
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
func (s *Storage) Get(ctx context.Context, key string, opts storage.GetOptions, objPtr runtime.Object) error {
var err error
req := &resource.ReadRequest{}
req := &resourcepb.ReadRequest{}
req.Key, err = s.getKey(key)
if err != nil {
if opts.IgnoreNotFound {
@@ -497,7 +499,7 @@ func (s *Storage) GuaranteedUpdate(
existingBytes []byte
err error
)
req := &resource.UpdateRequest{}
req := &resourcepb.UpdateRequest{}
req.Key, err = s.getKey(key)
if err != nil {
return err
@@ -511,7 +513,7 @@ func (s *Storage) GuaranteedUpdate(
for attempt := 1; attempt <= MaxUpdateAttempts; attempt = attempt + 1 {
// Read the latest value
readResponse, err := s.store.Read(ctx, &resource.ReadRequest{Key: req.Key})
readResponse, err := s.store.Read(ctx, &resourcepb.ReadRequest{Key: req.Key})
if err != nil {
return resource.GetError(resource.AsErrorResult(err))
}
@@ -587,7 +589,7 @@ func (s *Storage) GuaranteedUpdate(
req.Value, err = s.prepareObjectForUpdate(ctx, updatedObj, existingObj)
if err != nil {
return s.handleManagedResourceRouting(ctx, err, resource.WatchEvent_MODIFIED, key, updatedObj, destination)
return s.handleManagedResourceRouting(ctx, err, resourcepb.WatchEvent_MODIFIED, key, updatedObj, destination)
}
var rv uint64
+10 -9
View File
@@ -29,10 +29,11 @@ import (
"k8s.io/apiserver/pkg/storage/storagebackend"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
storagetesting "github.com/grafana/grafana/pkg/apiserver/storage/testing"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func init() {
@@ -160,17 +161,17 @@ func TestDeleteWithSuggestionAndConflict(t *testing.T) {
}
type resourceClientMock struct {
resource.ResourceStoreClient
resource.ResourceIndexClient
resource.ManagedObjectIndexClient
resource.BulkStoreClient
resource.BlobStoreClient
resource.DiagnosticsClient
resourcepb.ResourceStoreClient
resourcepb.ResourceIndexClient
resourcepb.ManagedObjectIndexClient
resourcepb.BulkStoreClient
resourcepb.BlobStoreClient
resourcepb.DiagnosticsClient
}
// always return GRPC Unauthenticated code
func (r resourceClientMock) List(ctx context.Context, in *resource.ListRequest, opts ...grpc.CallOption) (*resource.ListResponse, error) {
return &resource.ListResponse{}, status.Error(codes.Unauthenticated, "missing token")
func (r resourceClientMock) List(ctx context.Context, in *resourcepb.ListRequest, opts ...grpc.CallOption) (*resourcepb.ListResponse, error) {
return &resourcepb.ListResponse{}, status.Error(codes.Unauthenticated, "missing token")
}
func TestGRPCtoHTTPStatusMapping(t *testing.T) {
+10 -10
View File
@@ -15,11 +15,11 @@ import (
"k8s.io/klog/v2"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type streamDecoder struct {
client resource.ResourceStore_WatchClient
client resourcepb.ResourceStore_WatchClient
newFunc func() runtime.Object
predicate storage.SelectionPredicate
codec runtime.Codec
@@ -27,7 +27,7 @@ type streamDecoder struct {
done sync.WaitGroup
}
func newStreamDecoder(client resource.ResourceStore_WatchClient, newFunc func() runtime.Object, predicate storage.SelectionPredicate, codec runtime.Codec, cancelWatch context.CancelFunc) *streamDecoder {
func newStreamDecoder(client resourcepb.ResourceStore_WatchClient, newFunc func() runtime.Object, predicate storage.SelectionPredicate, codec runtime.Codec, cancelWatch context.CancelFunc) *streamDecoder {
return &streamDecoder{
client: client,
newFunc: newFunc,
@@ -36,7 +36,7 @@ func newStreamDecoder(client resource.ResourceStore_WatchClient, newFunc func()
cancelWatch: cancelWatch,
}
}
func (d *streamDecoder) toObject(w *resource.WatchEvent_Resource) (runtime.Object, error) {
func (d *streamDecoder) toObject(w *resourcepb.WatchEvent_Resource) (runtime.Object, error) {
var obj runtime.Object
var err error
obj, _, err = d.codec.Decode(w.Value, nil, d.newFunc())
@@ -56,7 +56,7 @@ func (d *streamDecoder) Decode() (action watch.EventType, object runtime.Object,
defer d.done.Done()
decode:
for {
var evt *resource.WatchEvent
var evt *resourcepb.WatchEvent
var err error
select {
case <-d.client.Context().Done():
@@ -79,7 +79,7 @@ decode:
}
// Error event
if evt.Type == resource.WatchEvent_ERROR {
if evt.Type == resourcepb.WatchEvent_ERROR {
err = fmt.Errorf("stream error")
klog.Errorf("client: error receiving result: %s", err)
return watch.Error, nil, err
@@ -90,7 +90,7 @@ decode:
continue decode
}
if evt.Type == resource.WatchEvent_BOOKMARK {
if evt.Type == resourcepb.WatchEvent_BOOKMARK {
obj := d.newFunc()
// here k8s expects an empty object with just resource version and k8s.io/initial-events-end annotation
@@ -113,7 +113,7 @@ decode:
var watchAction watch.EventType
switch evt.Type {
case resource.WatchEvent_ADDED:
case resourcepb.WatchEvent_ADDED:
// apply any predicates not handled in storage
matches, err := d.predicate.Matches(obj)
if err != nil {
@@ -125,7 +125,7 @@ decode:
}
watchAction = watch.Added
case resource.WatchEvent_MODIFIED:
case resourcepb.WatchEvent_MODIFIED:
watchAction = watch.Modified
// apply any predicates not handled in storage
@@ -175,7 +175,7 @@ decode:
// if the object didn't previously match, send an Added event
watchAction = watch.Added
}
case resource.WatchEvent_DELETED:
case resourcepb.WatchEvent_DELETED:
watchAction = watch.Deleted
// if we have a previous object, return that in the deleted event
+11 -12
View File
@@ -15,15 +15,14 @@ import (
"k8s.io/apiserver/pkg/storage"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource.ListRequest, storage.SelectionPredicate, error) {
func toListRequest(k *resourcepb.ResourceKey, opts storage.ListOptions) (*resourcepb.ListRequest, storage.SelectionPredicate, error) {
predicate := opts.Predicate
req := &resource.ListRequest{
req := &resourcepb.ListRequest{
Limit: opts.Predicate.Limit,
Options: &resource.ListOptions{
Options: &resourcepb.ListOptions{
Key: k,
},
NextPageToken: predicate.Continue,
@@ -39,11 +38,11 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource
switch opts.ResourceVersionMatch {
case "":
req.VersionMatchV2 = resource.ResourceVersionMatchV2_Unset
req.VersionMatchV2 = resourcepb.ResourceVersionMatchV2_Unset
case metav1.ResourceVersionMatchNotOlderThan:
req.VersionMatchV2 = resource.ResourceVersionMatchV2_NotOlderThan
req.VersionMatchV2 = resourcepb.ResourceVersionMatchV2_NotOlderThan
case metav1.ResourceVersionMatchExact:
req.VersionMatchV2 = resource.ResourceVersionMatchV2_Exact
req.VersionMatchV2 = resourcepb.ResourceVersionMatchV2_Exact
default:
return nil, predicate, apierrors.NewBadRequest(
fmt.Sprintf("unsupported version match: %v", opts.ResourceVersionMatch),
@@ -85,12 +84,12 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource
switch v {
case utils.LabelKeyGetTrash:
req.Source = resource.ListRequest_TRASH
req.Source = resourcepb.ListRequest_TRASH
if vals[0] != "true" {
return nil, predicate, apierrors.NewBadRequest("expecting true for: " + v)
}
case utils.LabelKeyGetHistory:
req.Source = resource.ListRequest_HISTORY
req.Source = resourcepb.ListRequest_HISTORY
req.Options.Key.Name = vals[0]
}
@@ -99,7 +98,7 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource
return req, storage.Everything, nil
}
req.Options.Labels = append(req.Options.Labels, &resource.Requirement{
req.Options.Labels = append(req.Options.Labels, &resourcepb.Requirement{
Key: v,
Operator: string(r.Operator()),
Values: r.Values().List(),
@@ -110,7 +109,7 @@ func toListRequest(k *resource.ResourceKey, opts storage.ListOptions) (*resource
if opts.Predicate.Field != nil && !opts.Predicate.Field.Empty() {
requirements := opts.Predicate.Field.Requirements()
for _, r := range requirements {
requirement := &resource.Requirement{Key: r.Field, Operator: string(r.Operator)}
requirement := &resourcepb.Requirement{Key: r.Field, Operator: string(r.Operator)}
if r.Value != "" {
requirement.Values = append(requirement.Values, r.Value)
}
+31 -31
View File
@@ -11,30 +11,30 @@ import (
"k8s.io/apiserver/pkg/storage"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestToListRequest(t *testing.T) {
tests := []struct {
name string
key *resource.ResourceKey
key *resourcepb.ResourceKey
opts storage.ListOptions
want *resource.ListRequest
want *resourcepb.ListRequest
wantPredicate storage.SelectionPredicate
wantErr error
}{
{
name: "basic list request",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
},
opts: storage.ListOptions{},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -46,7 +46,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with resource version",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -54,11 +54,11 @@ func TestToListRequest(t *testing.T) {
opts: storage.ListOptions{
ResourceVersion: "123",
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
ResourceVersion: 123,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -70,7 +70,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "invalid resource version",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -84,7 +84,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with label selector",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -94,15 +94,15 @@ func TestToListRequest(t *testing.T) {
Label: labels.SelectorFromSet(labels.Set{"key": "value"}),
},
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
},
Labels: []*resource.Requirement{
Labels: []*resourcepb.Requirement{
{
Key: "key",
Operator: string(selection.Equals),
@@ -118,7 +118,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with trash label",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -128,13 +128,13 @@ func TestToListRequest(t *testing.T) {
Label: labels.SelectorFromSet(labels.Set{utils.LabelKeyGetTrash: "true"}),
},
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Source: resource.ListRequest_TRASH,
Options: &resource.ListOptions{
Source: resourcepb.ListRequest_TRASH,
Options: &resourcepb.ListOptions{
Labels: nil,
Fields: nil,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -146,7 +146,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with history label",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -156,13 +156,13 @@ func TestToListRequest(t *testing.T) {
Label: labels.SelectorFromSet(labels.Set{utils.LabelKeyGetHistory: "test-name"}),
},
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Source: resource.ListRequest_HISTORY,
Options: &resource.ListOptions{
Source: resourcepb.ListRequest_HISTORY,
Options: &resourcepb.ListOptions{
Labels: nil,
Fields: nil,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -175,7 +175,7 @@ func TestToListRequest(t *testing.T) {
},
{
name: "with fullpath label",
key: &resource.ResourceKey{
key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
@@ -185,12 +185,12 @@ func TestToListRequest(t *testing.T) {
Label: labels.SelectorFromSet(labels.Set{utils.LabelGetFullpath: "true"}),
},
},
want: &resource.ListRequest{
want: &resourcepb.ListRequest{
VersionMatchV2: 1,
Options: &resource.ListOptions{
Options: &resourcepb.ListOptions{
Labels: nil,
Fields: nil,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Namespace: "default",
+4 -3
View File
@@ -37,6 +37,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql"
"github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl"
"github.com/grafana/grafana/pkg/tests/testsuite"
@@ -130,7 +131,7 @@ func testSetup(t testing.TB, opts ...setupOption) (context.Context, storage.Inte
require.NoError(t, err)
// Issue a health check to ensure the server is initialized
_, err = server.IsHealthy(ctx, &resource.HealthCheckRequest{})
_, err = server.IsHealthy(ctx, &resourcepb.HealthCheckRequest{})
require.NoError(t, err)
case StorageTypeUnified:
if testing.Short() {
@@ -377,7 +378,7 @@ func newPodList() runtime.Object {
return &example.PodList{}
}
func testKeyParser(val string) (*resource.ResourceKey, error) {
func testKeyParser(val string) (*resourcepb.ResourceKey, error) {
k, err := grafanaregistry.ParseKey(val)
if err != nil {
if strings.HasPrefix(val, "pods/") {
@@ -407,7 +408,7 @@ func testKeyParser(val string) (*resource.ResourceKey, error) {
if k.Resource == "" {
return nil, apierrors.NewInternalError(fmt.Errorf("missing resource in request"))
}
return &resource.ResourceKey{
return &resourcepb.ResourceKey{
Namespace: k.Namespace,
Group: k.Group,
Resource: k.Resource,
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func NewFederatedClient(base resource.ResourceClient, sql legacysql.LegacyDatabaseProvider) resource.ResourceClient {
@@ -26,7 +27,7 @@ type federatedClient struct {
}
// Get the resource stats
func (s *federatedClient) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
func (s *federatedClient) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest, opts ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) {
rsp, err := s.ResourceClient.GetStats(ctx, in, opts...)
if err != nil {
return nil, err
@@ -31,7 +31,7 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/federated"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/tests/testsuite"
)
@@ -114,7 +114,7 @@ func TestDirectSQLStats(t *testing.T) {
ctx := context.Background()
ctx = request.WithNamespace(ctx, "default")
stats, err := store.GetStats(ctx, &resource.ResourceStatsRequest{
stats, err := store.GetStats(ctx, &resourcepb.ResourceStatsRequest{
Namespace: "default",
Folder: folder1UID,
})
@@ -147,7 +147,7 @@ func TestDirectSQLStats(t *testing.T) {
ctx := context.Background()
ctx = request.WithNamespace(ctx, "default")
stats, err := store.GetStats(ctx, &resource.ResourceStatsRequest{
stats, err := store.GetStats(ctx, &resourcepb.ResourceStatsRequest{
Namespace: "default",
Folder: folder2UID,
})
+5 -4
View File
@@ -5,9 +5,10 @@ import (
"fmt"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Read stats from legacy SQL
@@ -15,7 +16,7 @@ type LegacyStatsGetter struct {
SQL legacysql.LegacyDatabaseProvider
}
func (s *LegacyStatsGetter) GetStats(ctx context.Context, in *resource.ResourceStatsRequest) (*resource.ResourceStatsResponse, error) {
func (s *LegacyStatsGetter) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest) (*resourcepb.ResourceStatsResponse, error) {
info, err := claims.ParseNamespace(in.Namespace)
if err != nil {
return nil, fmt.Errorf("unable to read namespace")
@@ -29,7 +30,7 @@ func (s *LegacyStatsGetter) GetStats(ctx context.Context, in *resource.ResourceS
return nil, err
}
rsp := &resource.ResourceStatsResponse{}
rsp := &resourcepb.ResourceStatsResponse{}
err = helper.DB.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
fn := func(table, where, g, r string, existCheck bool) error {
// if existCheck is true, do not error out if the table does not exist
@@ -46,7 +47,7 @@ func (s *LegacyStatsGetter) GetStats(ctx context.Context, in *resource.ResourceS
if err != nil {
return err
}
rsp.Stats = append(rsp.Stats, &resource.ResourceStatsResponse_Stats{
rsp.Stats = append(rsp.Stats, &resourcepb.ResourceStatsResponse_Stats{
Group: g, // all legacy for now
Resource: r,
Count: count,
+8 -7
View File
@@ -9,11 +9,12 @@ import (
"google.golang.org/grpc/metadata"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var (
_ resource.BulkStoreClient = (*writerClient)(nil)
_ resource.BulkStore_BulkProcessClient = (*writerClient)(nil)
_ resourcepb.BulkStoreClient = (*writerClient)(nil)
_ resourcepb.BulkStore_BulkProcessClient = (*writerClient)(nil)
errUnimplemented = errors.New("not implemented (BulkResourceWriter as BulkStoreClient shim)")
)
@@ -29,12 +30,12 @@ func NewBulkResourceWriterClient(writer resource.BulkResourceWriter) *writerClie
}
// Send implements resource.ResourceStore_BulkProcessClient.
func (w *writerClient) Send(req *resource.BulkRequest) error {
func (w *writerClient) Send(req *resourcepb.BulkRequest) error {
return w.writer.Write(w.ctx, req.Key, req.Value)
}
// BulkProcess implements resource.ResourceStoreClient.
func (w *writerClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resource.BulkStore_BulkProcessClient, error) {
func (w *writerClient) BulkProcess(ctx context.Context, _ ...grpc.CallOption) (resourcepb.BulkStore_BulkProcessClient, error) {
if w.ctx != nil {
return nil, fmt.Errorf("only one batch request supported")
}
@@ -43,7 +44,7 @@ func (w *writerClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption)
}
// CloseAndRecv implements resource.ResourceStore_BulkProcessClient.
func (w *writerClient) CloseAndRecv() (*resource.BulkResponse, error) {
func (w *writerClient) CloseAndRecv() (*resourcepb.BulkResponse, error) {
return w.writer.CloseWithResults()
}
@@ -63,12 +64,12 @@ func (w *writerClient) Header() (metadata.MD, error) {
}
// RecvMsg implements resource.ResourceStore_BulkProcessClient.
func (w *writerClient) RecvMsg(m any) error {
func (w *writerClient) RecvMsg(_ any) error {
return errUnimplemented
}
// SendMsg implements resource.ResourceStore_BulkProcessClient.
func (w *writerClient) SendMsg(m any) error {
func (w *writerClient) SendMsg(_ any) error {
return errUnimplemented
}
+6 -5
View File
@@ -7,6 +7,7 @@ import (
"github.com/apache/arrow-go/v18/parquet/file"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var (
@@ -39,7 +40,7 @@ type parquetReader struct {
bufferIndex int
rowGroupIDX int
req *resource.BulkRequest
req *resourcepb.BulkRequest
err error
}
@@ -60,14 +61,14 @@ func (r *parquetReader) Next() bool {
i := r.bufferIndex
r.bufferIndex++
r.req = &resource.BulkRequest{
Key: &resource.ResourceKey{
r.req = &resourcepb.BulkRequest{
Key: &resourcepb.ResourceKey{
Group: r.group.buffer[i].String(),
Resource: r.resource.buffer[i].String(),
Namespace: r.namespace.buffer[i].String(),
Name: r.name.buffer[i].String(),
},
Action: resource.BulkRequest_Action(r.action.buffer[i]),
Action: resourcepb.BulkRequest_Action(r.action.buffer[i]),
Value: r.value.buffer[i].Bytes(),
Folder: r.folder.buffer[i].String(),
}
@@ -88,7 +89,7 @@ func (r *parquetReader) Next() bool {
}
// Request implements resource.BulkRequestIterator.
func (r *parquetReader) Request() *resource.BulkRequest {
func (r *parquetReader) Request() *resourcepb.BulkRequest {
return r.req
}
+5 -4
View File
@@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestParquetWriteThenRead(t *testing.T) {
@@ -75,7 +76,7 @@ func TestParquetWriteThenRead(t *testing.T) {
require.NoError(t, err)
for reader.Next() {
req := reader.Request()
keys = append(keys, req.Key.SearchID())
keys = append(keys, resource.SearchID(req.Key))
}
// Verify that we read all values
@@ -101,14 +102,14 @@ func TestParquetWriteThenRead(t *testing.T) {
require.NoError(t, err)
for reader.Next() {
req := reader.Request()
keys = append(keys, req.Key.SearchID())
keys = append(keys, resource.SearchID(req.Key))
}
require.NoError(t, reader.err)
require.Empty(t, keys)
})
}
func toKeyAndBytes(ctx context.Context, group string, res string, obj *unstructured.Unstructured) (context.Context, *resource.ResourceKey, []byte) {
func toKeyAndBytes(ctx context.Context, group string, res string, obj *unstructured.Unstructured) (context.Context, *resourcepb.ResourceKey, []byte) {
if obj.GetKind() == "" {
obj.SetKind(res)
}
@@ -116,7 +117,7 @@ func toKeyAndBytes(ctx context.Context, group string, res string, obj *unstructu
obj.SetAPIVersion(group + "/vXyz")
}
data, _ := obj.MarshalJSON()
return ctx, &resource.ResourceKey{
return ctx, &resourcepb.ResourceKey{
Namespace: obj.GetNamespace(),
Resource: res,
Group: group,
+17 -15
View File
@@ -13,8 +13,10 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var (
@@ -28,8 +30,8 @@ func NewParquetWriter(f io.Writer) (*parquetWriter, error) {
schema: newSchema(nil),
buffer: 1024 * 10 * 100 * 10, // 10MB
logger: logging.DefaultLogger.With("logger", "parquet.writer"),
rsp: &resource.BulkResponse{},
summary: make(map[string]*resource.BulkResponse_Summary),
rsp: &resourcepb.BulkResponse{},
summary: make(map[string]*resourcepb.BulkResponse_Summary),
}
props := parquet.NewWriterProperties(
@@ -44,7 +46,7 @@ func NewParquetWriter(f io.Writer) (*parquetWriter, error) {
}
// ProcessBulk implements resource.BulkProcessingBackend.
func (w *parquetWriter) ProcessBulk(ctx context.Context, setting resource.BulkSettings, iter resource.BulkRequestIterator) *resource.BulkResponse {
func (w *parquetWriter) ProcessBulk(ctx context.Context, setting resource.BulkSettings, iter resource.BulkRequestIterator) *resourcepb.BulkResponse {
defer func() { _ = w.Close() }()
var err error
@@ -66,7 +68,7 @@ func (w *parquetWriter) ProcessBulk(ctx context.Context, setting resource.BulkSe
w.logger.Warn("error closing parquet file", "err", err)
}
if rsp == nil {
rsp = &resource.BulkResponse{}
rsp = &resourcepb.BulkResponse{}
}
if err != nil {
rsp.Error = resource.AsErrorResult(err)
@@ -92,11 +94,11 @@ type parquetWriter struct {
action *array.Int8Builder
value *array.StringBuilder
rsp *resource.BulkResponse
summary map[string]*resource.BulkResponse_Summary
rsp *resourcepb.BulkResponse
summary map[string]*resourcepb.BulkResponse_Summary
}
func (w *parquetWriter) CloseWithResults() (*resource.BulkResponse, error) {
func (w *parquetWriter) CloseWithResults() (*resourcepb.BulkResponse, error) {
err := w.Close()
return w.rsp, err
}
@@ -143,7 +145,7 @@ func (w *parquetWriter) init() error {
return nil
}
func (w *parquetWriter) Write(ctx context.Context, key *resource.ResourceKey, value []byte) error {
func (w *parquetWriter) Write(ctx context.Context, key *resourcepb.ResourceKey, value []byte) error {
w.rsp.Processed++
obj := &unstructured.Unstructured{}
err := obj.UnmarshalJSON(value)
@@ -164,14 +166,14 @@ func (w *parquetWriter) Write(ctx context.Context, key *resource.ResourceKey, va
w.folder.Append(meta.GetFolder())
w.value.Append(string(value))
var action resource.WatchEvent_Type
var action resourcepb.WatchEvent_Type
switch meta.GetGeneration() {
case 0, 1:
action = resource.WatchEvent_ADDED
action = resourcepb.WatchEvent_ADDED
case utils.DeletedGeneration:
action = resource.WatchEvent_DELETED
action = resourcepb.WatchEvent_DELETED
default:
action = resource.WatchEvent_MODIFIED
action = resourcepb.WatchEvent_MODIFIED
}
w.action.Append(int8(action))
@@ -181,14 +183,14 @@ func (w *parquetWriter) Write(ctx context.Context, key *resource.ResourceKey, va
return w.flush()
}
summary := w.summary[key.NSGR()]
summary := w.summary[resource.NSGR(key)]
if summary == nil {
summary = &resource.BulkResponse_Summary{
summary = &resourcepb.BulkResponse_Summary{
Namespace: key.Namespace,
Group: key.Group,
Resource: key.Resource,
}
w.summary[key.NSGR()] = summary
w.summary[resource.NSGR(key)] = summary
w.rsp.Summary = append(w.rsp.Summary, summary)
}
summary.Count++
+98
View File
@@ -0,0 +1,98 @@
syntax = "proto3";
package resource;
option go_package = "github.com/grafana/grafana/pkg/storage/unified/resourcepb";
import "resource.proto";
//----------------------------
// Blob Support
//----------------------------
message PutBlobRequest {
enum Method {
// Use the inline raw []byte
GRPC = 0;
// Get a signed URL and PUT the value
HTTP = 1;
}
// The resource that will use this blob
// NOTE: the name may not yet exist, but group+resource are required
ResourceKey resource = 1;
// How to upload
Method method = 2;
// Content type header
string content_type = 3;
// Raw value to write
// Not valid when method == HTTP
bytes value = 4;
}
message PutBlobResponse {
// Error details
ErrorResult error = 1;
// The blob uid. This must be saved into the resource to support access
string uid = 2;
// The URL where this value can be PUT
string url = 3;
// Size of the uploaded blob
int64 size = 4;
// Content hash used for an etag
string hash = 5;
// Validated mimetype (from content_type)
string mime_type = 6;
// Validated charset (from content_type)
string charset = 7;
}
message GetBlobRequest {
ResourceKey resource = 1;
// The new resource version
int64 resource_version = 2;
// Do not return a pre-signed URL (when possible)
bool must_proxy_bytes = 3;
// The blob UID -- when empty, the value is loaded from annotations in the matching resource
string uid = 4;
}
message GetBlobResponse {
// Error details
ErrorResult error = 1;
// (optional) When possible, the system will return a presigned URL
// that can be used to actually read the full blob+metadata
// When this is set, neither info nor value will be set
string url = 2;
// Content type
string content_type = 3;
// The raw object value
bytes value = 4;
}
service BlobStore {
// Upload a blob that will be saved in a resource
rpc PutBlob(PutBlobRequest) returns (PutBlobResponse);
// Get blob contents. When possible, this will return a signed URL
// For large payloads, signed URLs are required to avoid protobuf message size limits
rpc GetBlob(GetBlobRequest) returns (GetBlobResponse);
// NOTE: there is no direct access to delete blobs
// >> cleanup will be managed via garbage collection or direct access to the underlying storage
}
@@ -1,10 +1,10 @@
version: v1
plugins:
- plugin: go
out: pkg/storage/unified/resource
out: pkg/storage/unified/resourcepb
opt: paths=source_relative
- plugin: go-grpc
out: pkg/storage/unified/resource
out: pkg/storage/unified/resourcepb
opt:
- paths=source_relative
- require_unimplemented_servers=false
@@ -1,4 +1,6 @@
version: v1
version: v2
modules:
- path: .
breaking:
use:
- FILE
@@ -1,7 +1,7 @@
syntax = "proto3";
package resource;
option go_package = "github.com/grafana/grafana/pkg/storage/unified/resource";
option go_package = "github.com/grafana/grafana/pkg/storage/unified/resourcepb";
message ResourceKey {
// Namespace (tenant)
@@ -331,7 +331,7 @@ message WatchEvent {
message BulkRequest {
enum Action {
// will be an error
UNKNOWN = 0;
UNKNOWN = 0;
// Matches Watch event enum
ADDED = 1;
@@ -345,7 +345,7 @@ message BulkRequest {
// Requested action
Action action = 2;
// The resource value
// The resource value
bytes value = 3;
// Hint that a new version will be written on-top of this
@@ -382,135 +382,10 @@ message BulkResponse {
// Summary status for the processed values
repeated Summary summary = 3;
// Rejected
// Rejected
repeated Rejected rejected = 4;
}
// Get statistics across multiple resources
// For these queries, we do not need authorization to see the actual values
message ResourceStatsRequest {
// Namespace (tenant)
string namespace = 1;
// An optional list of group/resource identifiers
// when empty, we assume searching across everything
// NOTE, this query may need to federate across a few storage instances
repeated string kinds = 2;
// Limit the stats within a folder (not recursive!)
string folder = 3;
}
message ResourceStatsResponse {
message Stats {
// Resource group
string group = 1;
// Resource name
string resource = 2;
// Number of items
int64 count = 3;
}
// Error details
ErrorResult error = 1;
// All results exist within this key
repeated Stats stats = 2;
}
// Search within a single resource
message ResourceSearchRequest {
message Sort {
string field = 1;
bool desc = 2; // defaults to ascending
}
message Facet {
string field = 1;
int64 limit = 2;
// For now, only term queries, eventually?
// numeric queries
// date queries
}
// The key must include namespace + group + resource
ListOptions options = 1;
// To search additional resource types, add additional keys to this list
// NOTE: queries will only support federation across kinds with common fields
repeated ResourceKey federated = 2;
// When a query exists, it is parsed and used to influence
// query string for chosen implementation (currently just bleve)
// The score is only relevant when a query exists
string query = 3;
// max results
int64 limit = 4;
// where to start the query (eg, From)
int64 offset = 5;
// sorting
repeated Sort sortBy = 6;
// calculate field statistics
map<string,Facet> facet = 7;
// the return fields (empty will return everything)
repeated string fields = 8;
// explain each result (added to the each row)
bool explain = 9;
bool is_deleted = 10;
int64 page = 11;
int64 permission = 12;
}
message ResourceSearchResponse {
message Facet {
string field = 1;
// The distinct terms
int64 total = 2;
// The number of documents that do *not* have this field
int64 missing = 3;
// Top term stats
repeated TermFacet terms = 4;
// numeric range
// date range facets
}
message TermFacet {
string term = 1;
int64 count = 2;
}
// Error details
ErrorResult error = 1;
// All results exist within this key
ResourceKey key = 2;
// Query results
ResourceTable results = 3;
// The total hit count
int64 total_hits = 4;
// indicates how expensive was the query with respect to bytes read
double query_cost = 5;
// maximum score across all fields
double max_score = 6;
// Facet results
map<string,Facet> facet = 7;
}
// List items within a resource type & repository name
// Access control is managed above this request
message ListManagedObjectsRequest {
@@ -712,86 +587,6 @@ message ResourceTableRow {
bytes object = 4;
}
//----------------------------
// Blob Support
//----------------------------
message PutBlobRequest {
enum Method {
// Use the inline raw []byte
GRPC = 0;
// Get a signed URL and PUT the value
HTTP = 1;
}
// The resource that will use this blob
// NOTE: the name may not yet exist, but group+resource are required
ResourceKey resource = 1;
// How to upload
Method method = 2;
// Content type header
string content_type = 3;
// Raw value to write
// Not valid when method == HTTP
bytes value = 4;
}
message PutBlobResponse {
// Error details
ErrorResult error = 1;
// The blob uid. This must be saved into the resource to support access
string uid = 2;
// The URL where this value can be PUT
string url = 3;
// Size of the uploaded blob
int64 size = 4;
// Content hash used for an etag
string hash = 5;
// Validated mimetype (from content_type)
string mime_type = 6;
// Validated charset (from content_type)
string charset = 7;
}
message GetBlobRequest {
ResourceKey resource = 1;
// The new resource version
int64 resource_version = 2;
// Do not return a pre-signed URL (when possible)
bool must_proxy_bytes = 3;
// The blob UID -- when empty, the value is loaded from annotations in the matching resource
string uid = 4;
}
message GetBlobResponse {
// Error details
ErrorResult error = 1;
// (optional) When possible, the system will return a presigned URL
// that can be used to actually read the full blob+metadata
// When this is set, neither info nor value will be set
string url = 2;
// Content type
string content_type = 3;
// The raw object value
bytes value = 4;
}
// This provides the CRUD+List+Watch support needed for a k8s apiserver
// The semantics and behaviors of this service are constrained by kubernetes
// This does not understand the resource schemas, only deals with json bytes
@@ -820,15 +615,6 @@ service BulkStore {
rpc BulkProcess(stream BulkRequest) returns (BulkResponse);
}
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
service ResourceIndex {
rpc Search(ResourceSearchRequest) returns (ResourceSearchResponse);
// Get the resource stats
rpc GetStats(ResourceStatsRequest) returns (ResourceStatsResponse);
}
// Query managed objects
// Results access control is based on access to the repository *not* the items
service ManagedObjectIndex {
@@ -839,18 +625,6 @@ service ManagedObjectIndex {
rpc ListManagedObjects(ListManagedObjectsRequest) returns (ListManagedObjectsResponse);
}
service BlobStore {
// Upload a blob that will be saved in a resource
rpc PutBlob(PutBlobRequest) returns (PutBlobResponse);
// Get blob contents. When possible, this will return a signed URL
// For large payloads, signed URLs are required to avoid protobuf message size limits
rpc GetBlob(GetBlobRequest) returns (GetBlobResponse);
// NOTE: there is no direct access to delete blobs
// >> cleanup will be managed via garbage collection or direct access to the underlying storage
}
// Clients can use this service directly
// NOTE: This is read only, and no read afer write guarantees
service Diagnostics {
+140
View File
@@ -0,0 +1,140 @@
syntax = "proto3";
package resource;
option go_package = "github.com/grafana/grafana/pkg/storage/unified/resourcepb";
import "resource.proto";
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
service ResourceIndex {
rpc Search(ResourceSearchRequest) returns (ResourceSearchResponse);
// Get the resource stats
rpc GetStats(ResourceStatsRequest) returns (ResourceStatsResponse);
}
// Get statistics across multiple resources
// For these queries, we do not need authorization to see the actual values
message ResourceStatsRequest {
// Namespace (tenant)
string namespace = 1;
// An optional list of group/resource identifiers
// when empty, we assume searching across everything
// NOTE, this query may need to federate across a few storage instances
repeated string kinds = 2;
// Limit the stats within a folder (not recursive!)
string folder = 3;
}
message ResourceStatsResponse {
message Stats {
// Resource group
string group = 1;
// Resource name
string resource = 2;
// Number of items
int64 count = 3;
}
// Error details
ErrorResult error = 1;
// All results exist within this key
repeated Stats stats = 2;
}
// Search within a single resource
message ResourceSearchRequest {
message Sort {
string field = 1;
bool desc = 2; // defaults to ascending
}
message Facet {
string field = 1;
int64 limit = 2;
// For now, only term queries, eventually?
// numeric queries
// date queries
}
// The key must include namespace + group + resource
ListOptions options = 1;
// To search additional resource types, add additional keys to this list
// NOTE: queries will only support federation across kinds with common fields
repeated ResourceKey federated = 2;
// When a query exists, it is parsed and used to influence
// query string for chosen implementation (currently just bleve)
// The score is only relevant when a query exists
string query = 3;
// max results
int64 limit = 4;
// where to start the query (eg, From)
int64 offset = 5;
// sorting
repeated Sort sortBy = 6;
// calculate field statistics
map<string,Facet> facet = 7;
// the return fields (empty will return everything)
repeated string fields = 8;
// explain each result (added to the each row)
bool explain = 9;
bool is_deleted = 10;
int64 page = 11;
int64 permission = 12;
}
message ResourceSearchResponse {
message Facet {
string field = 1;
// The distinct terms
int64 total = 2;
// The number of documents that do *not* have this field
int64 missing = 3;
// Top term stats
repeated TermFacet terms = 4;
// numeric range
// date range facets
}
message TermFacet {
string term = 1;
int64 count = 2;
}
// Error details
ErrorResult error = 1;
// All results exist within this key
ResourceKey key = 2;
// Query results
ResourceTable results = 3;
// The total hit count
int64 total_hits = 4;
// indicates how expensive was the query with respect to bytes read
double query_cost = 5;
// maximum score across all fields
double max_score = 6;
// Facet results
map<string,Facet> facet = 7;
}
+35 -33
View File
@@ -10,7 +10,9 @@ import (
"google.golang.org/grpc/metadata"
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
const grpcMetaKeyCollection = "x-gf-batch-collection"
@@ -25,28 +27,28 @@ type BulkRequestIterator interface {
Next() bool
// The next event we should process
Request() *BulkRequest
Request() *resourcepb.BulkRequest
// Rollback requested
RollbackRequested() bool
}
type BulkProcessingBackend interface {
ProcessBulk(ctx context.Context, setting BulkSettings, iter BulkRequestIterator) *BulkResponse
ProcessBulk(ctx context.Context, setting BulkSettings, iter BulkRequestIterator) *resourcepb.BulkResponse
}
type BulkResourceWriter interface {
io.Closer
Write(ctx context.Context, key *ResourceKey, value []byte) error
Write(ctx context.Context, key *resourcepb.ResourceKey, value []byte) error
// Called when finished writing
CloseWithResults() (*BulkResponse, error)
CloseWithResults() (*resourcepb.BulkResponse, error)
}
type BulkSettings struct {
// All requests will be within this namespace/group/resource
Collection []*ResourceKey
Collection []*resourcepb.ResourceKey
// The batch will include everything from the collection
// - all existing values will be removed/replaced if the batch completes successfully
@@ -60,7 +62,7 @@ func (x *BulkSettings) ToMD() metadata.MD {
md := make(metadata.MD)
if len(x.Collection) > 0 {
for _, v := range x.Collection {
md[grpcMetaKeyCollection] = append(md[grpcMetaKeyCollection], v.SearchID())
md[grpcMetaKeyCollection] = append(md[grpcMetaKeyCollection], SearchID(v))
}
}
if x.RebuildCollection {
@@ -78,8 +80,8 @@ func NewBulkSettings(md metadata.MD) (BulkSettings, error) {
switch k {
case grpcMetaKeyCollection:
for _, c := range v {
key := &ResourceKey{}
err := key.ReadSearchID(c)
key := &resourcepb.ResourceKey{}
err := ReadSearchID(key, c)
if err != nil {
return settings, fmt.Errorf("error reading collection metadata: %s / %w", c, err)
}
@@ -96,12 +98,12 @@ func NewBulkSettings(md metadata.MD) (BulkSettings, error) {
// BulkWrite implements ResourceServer.
// All requests must be to the same NAMESPACE/GROUP/RESOURCE
func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
func (s *server) BulkProcess(stream resourcepb.BulkStore_BulkProcessServer) error {
ctx := stream.Context()
user, ok := authlib.AuthInfoFrom(ctx)
if !ok || user == nil {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "no user found in context",
Code: http.StatusUnauthorized,
},
@@ -110,8 +112,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "unable to read metadata gRPC request",
Code: http.StatusPreconditionFailed,
},
@@ -123,8 +125,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}
settings, err := NewBulkSettings(md)
if err != nil {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "error reading settings",
Reason: err.Error(),
Code: http.StatusPreconditionFailed,
@@ -133,8 +135,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}
if len(settings.Collection) < 1 {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "Missing target collection(s) in request header",
Code: http.StatusBadRequest,
},
@@ -151,8 +153,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
Verb: utils.VerbDeleteCollection,
})
if err != nil || !rsp.Allowed {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: fmt.Sprintf("Requester must be able to: %s", utils.VerbDeleteCollection),
Code: http.StatusForbidden,
},
@@ -160,15 +162,15 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}
// This will be called for each request -- with the folder ID
runner.checker[k.NSGR()], err = s.access.Compile(ctx, user, authlib.ListRequest{
runner.checker[NSGR(k)], err = s.access.Compile(ctx, user, authlib.ListRequest{
Namespace: k.Namespace,
Group: k.Group,
Resource: k.Resource,
Verb: utils.VerbCreate,
})
if err != nil {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "Unable to check `create` permission",
Code: http.StatusForbidden,
},
@@ -176,8 +178,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}
}
} else {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "Bulk currently only supports RebuildCollection",
Code: http.StatusBadRequest,
},
@@ -186,8 +188,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
backend, ok := s.backend.(BulkProcessingBackend)
if !ok {
return stream.SendAndClose(&BulkResponse{
Error: &ErrorResult{
return stream.SendAndClose(&resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Message: "The server backend does not support batch processing",
Code: http.StatusNotImplemented,
},
@@ -197,8 +199,8 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
// BulkProcess requests
rsp := backend.ProcessBulk(ctx, settings, runner)
if rsp == nil {
rsp = &BulkResponse{
Error: &ErrorResult{
rsp = &resourcepb.BulkResponse{
Error: &resourcepb.ErrorResult{
Code: http.StatusInternalServerError,
Message: "Nothing returned from process batch",
},
@@ -218,7 +220,7 @@ func (s *server) BulkProcess(stream BulkStore_BulkProcessServer) error {
}, summary.Count, summary.ResourceVersion)
if err != nil {
s.log.Warn("error building search index after batch load", "err", err)
rsp.Error = &ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Code: http.StatusInternalServerError,
Message: "err building search index: " + summary.Resource,
Reason: err.Error(),
@@ -234,9 +236,9 @@ var (
)
type batchRunner struct {
stream BulkStore_BulkProcessServer
stream resourcepb.BulkStore_BulkProcessServer
rollback bool
request *BulkRequest
request *resourcepb.BulkRequest
err error
checker map[string]authlib.ItemChecker
}
@@ -262,7 +264,7 @@ func (b *batchRunner) Next() bool {
if b.request != nil {
key := b.request.Key
k := key.NSGR()
k := NSGR(key)
checker, ok := b.checker[k]
if !ok {
b.err = fmt.Errorf("missing access control for: %s", k)
@@ -277,7 +279,7 @@ func (b *batchRunner) Next() bool {
}
// Request implements BulkRequestIterator.
func (b *batchRunner) Request() *BulkRequest {
func (b *batchRunner) Request() *resourcepb.BulkRequest {
if b.rollback {
return nil
}
+12 -11
View File
@@ -23,6 +23,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type CDKBackendOptions struct {
@@ -73,7 +74,7 @@ type cdkBackend struct {
stream chan<- *WrittenEvent
}
func (s *cdkBackend) getPath(key *ResourceKey, rv int64) string {
func (s *cdkBackend) getPath(key *resourcepb.ResourceKey, rv int64) string {
var buffer bytes.Buffer
buffer.WriteString(s.root)
@@ -116,9 +117,9 @@ func (s *cdkBackend) GetResourceStats(ctx context.Context, namespace string, min
}
func (s *cdkBackend) WriteEvent(ctx context.Context, event WriteEvent) (rv int64, err error) {
if event.Type == WatchEvent_ADDED {
if event.Type == resourcepb.WatchEvent_ADDED {
// ReadResource deals with deleted values (i.e. a file exists but has generation -999).
resp := s.ReadResource(ctx, &ReadRequest{Key: event.Key})
resp := s.ReadResource(ctx, &resourcepb.ReadRequest{Key: event.Key})
if resp.Error != nil && resp.Error.Code != http.StatusNotFound {
return 0, GetError(resp.Error)
}
@@ -155,7 +156,7 @@ func (s *cdkBackend) WriteEvent(ctx context.Context, event WriteEvent) (rv int64
return rv, err
}
func (s *cdkBackend) ReadResource(ctx context.Context, req *ReadRequest) *BackendReadResponse {
func (s *cdkBackend) ReadResource(ctx context.Context, req *resourcepb.ReadRequest) *BackendReadResponse {
rv := req.ResourceVersion
path := s.getPath(req.Key, rv)
@@ -184,12 +185,12 @@ func (s *cdkBackend) ReadResource(ctx context.Context, req *ReadRequest) *Backen
if raw == nil && req.ResourceVersion > 0 {
if req.ResourceVersion > s.rv.Load() {
return &BackendReadResponse{
Error: &ErrorResult{
Error: &resourcepb.ErrorResult{
Code: http.StatusGatewayTimeout,
Reason: string(metav1.StatusReasonTimeout), // match etcd behavior
Message: "ResourceVersion is larger than max",
Details: &ErrorDetails{
Causes: []*ErrorCause{
Details: &resourcepb.ErrorDetails{
Causes: []*resourcepb.ErrorCause{
{
Reason: string(metav1.CauseTypeResourceVersionTooLarge),
Message: fmt.Sprintf("requested: %d, current %d", req.ResourceVersion, s.rv.Load()),
@@ -201,7 +202,7 @@ func (s *cdkBackend) ReadResource(ctx context.Context, req *ReadRequest) *Backen
}
// If the there was an explicit request, get the latest
rsp := s.ReadResource(ctx, &ReadRequest{Key: req.Key})
rsp := s.ReadResource(ctx, &resourcepb.ReadRequest{Key: req.Key})
if rsp != nil && len(rsp.Value) > 0 {
raw = rsp.Value
rv = rsp.ResourceVersion
@@ -233,8 +234,8 @@ func isDeletedValue(raw []byte) bool {
return false
}
func (s *cdkBackend) ListIterator(ctx context.Context, req *ListRequest, cb func(ListIterator) error) (int64, error) {
if req.Source != ListRequest_STORE {
func (s *cdkBackend) ListIterator(ctx context.Context, req *resourcepb.ListRequest, cb func(ListIterator) error) (int64, error) {
if req.Source != resourcepb.ListRequest_STORE {
return 0, fmt.Errorf("listing from history not supported in CDK backend")
}
@@ -356,7 +357,7 @@ func (c *cdkListIterator) Folder() string {
var _ ListIterator = (*cdkListIterator)(nil)
func buildTree(ctx context.Context, s *cdkBackend, key *ResourceKey) (*cdkListIterator, error) {
func buildTree(ctx context.Context, s *cdkBackend, key *resourcepb.ResourceKey) (*cdkListIterator, error) {
byPrefix := make(map[string]*cdkResource)
path := s.getPath(key, 0)
iter := s.bucket.List(&blob.ListOptions{Prefix: path, Delimiter: ""}) // "" is recursive
+9 -7
View File
@@ -2,7 +2,7 @@ package resource
import (
"bytes"
context "context"
"context"
"crypto/md5"
"encoding/hex"
"fmt"
@@ -16,6 +16,7 @@ import (
"gocloud.dev/blob"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
// Supported drivers
_ "gocloud.dev/blob/azureblob"
@@ -85,7 +86,7 @@ type cdkBlobSupport struct {
expiration time.Duration
}
func (s *cdkBlobSupport) getBlobPath(key *ResourceKey, info *utils.BlobInfo) (string, error) {
func (s *cdkBlobSupport) getBlobPath(key *resourcepb.ResourceKey, info *utils.BlobInfo) (string, error) {
var buffer bytes.Buffer
buffer.WriteString(s.root)
@@ -129,7 +130,7 @@ func (s *cdkBlobSupport) SupportsSignedURLs() bool {
return s.cansignurls
}
func (s *cdkBlobSupport) PutResourceBlob(ctx context.Context, req *PutBlobRequest) (*PutBlobResponse, error) {
func (s *cdkBlobSupport) PutResourceBlob(ctx context.Context, req *resourcepb.PutBlobRequest) (*resourcepb.PutBlobResponse, error) {
info := &utils.BlobInfo{
UID: uuid.New().String(),
}
@@ -139,8 +140,8 @@ func (s *cdkBlobSupport) PutResourceBlob(ctx context.Context, req *PutBlobReques
return nil, err
}
rsp := &PutBlobResponse{Uid: info.UID, MimeType: info.MimeType, Charset: info.Charset}
if req.Method == PutBlobRequest_HTTP {
rsp := &resourcepb.PutBlobResponse{Uid: info.UID, MimeType: info.MimeType, Charset: info.Charset}
if req.Method == resourcepb.PutBlobRequest_HTTP {
rsp.Url, err = s.bucket.SignedURL(ctx, path, &blob.SignedURLOptions{
Method: "PUT",
Expiry: s.expiration,
@@ -176,12 +177,13 @@ func (s *cdkBlobSupport) PutResourceBlob(ctx context.Context, req *PutBlobReques
return rsp, err
}
func (s *cdkBlobSupport) GetResourceBlob(ctx context.Context, resource *ResourceKey, info *utils.BlobInfo, mustProxy bool) (*GetBlobResponse, error) {
func (s *cdkBlobSupport) GetResourceBlob(ctx context.Context, resource *resourcepb.ResourceKey, info *utils.BlobInfo,
mustProxy bool) (*resourcepb.GetBlobResponse, error) {
path, err := s.getBlobPath(resource, info)
if err != nil {
return nil, err
}
rsp := &GetBlobResponse{ContentType: info.ContentType()}
rsp := &resourcepb.GetBlobResponse{ContentType: info.ContentType()}
if mustProxy || !s.cansignurls {
rsp.Value, err = s.bucket.ReadAll(ctx, path)
return rsp, err
@@ -7,6 +7,8 @@ import (
"testing"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/stretchr/testify/require"
"gocloud.dev/blob/fileblob"
"gocloud.dev/blob/memblob"
@@ -36,16 +38,16 @@ func TestCDKBlobStore(t *testing.T) {
t.Run("can write then read a blob", func(t *testing.T) {
raw := []byte(`{"hello": "world"}`)
key := &ResourceKey{
key := &resourcepb.ResourceKey{
Group: "playlist.grafana.app",
Resource: "rrrr", // can be anything
Namespace: "default",
Name: "fdgsv37qslr0ga",
}
rsp, err := store.PutResourceBlob(ctx, &PutBlobRequest{
rsp, err := store.PutResourceBlob(ctx, &resourcepb.PutBlobRequest{
Resource: key,
Method: PutBlobRequest_GRPC,
Method: resourcepb.PutBlobRequest_GRPC,
ContentType: "application/json",
Value: raw,
})
+37 -36
View File
@@ -23,36 +23,37 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
grpcUtils "github.com/grafana/grafana/pkg/storage/unified/resource/grpc"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type ResourceClient interface {
ResourceStoreClient
ResourceIndexClient
ManagedObjectIndexClient
BulkStoreClient
BlobStoreClient
DiagnosticsClient
resourcepb.ResourceStoreClient
resourcepb.ResourceIndexClient
resourcepb.ManagedObjectIndexClient
resourcepb.BulkStoreClient
resourcepb.BlobStoreClient
resourcepb.DiagnosticsClient
}
// Internal implementation
type resourceClient struct {
ResourceStoreClient
ResourceIndexClient
ManagedObjectIndexClient
BulkStoreClient
BlobStoreClient
DiagnosticsClient
resourcepb.ResourceStoreClient
resourcepb.ResourceIndexClient
resourcepb.ManagedObjectIndexClient
resourcepb.BulkStoreClient
resourcepb.BlobStoreClient
resourcepb.DiagnosticsClient
}
func NewLegacyResourceClient(channel grpc.ClientConnInterface) ResourceClient {
cc := grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc),
ManagedObjectIndexClient: NewManagedObjectIndexClient(cc),
BulkStoreClient: NewBulkStoreClient(cc),
BlobStoreClient: NewBlobStoreClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
ResourceStoreClient: resourcepb.NewResourceStoreClient(cc),
ResourceIndexClient: resourcepb.NewResourceIndexClient(cc),
ManagedObjectIndexClient: resourcepb.NewManagedObjectIndexClient(cc),
BulkStoreClient: resourcepb.NewBulkStoreClient(cc),
BlobStoreClient: resourcepb.NewBlobStoreClient(cc),
DiagnosticsClient: resourcepb.NewDiagnosticsClient(cc),
}
}
@@ -63,12 +64,12 @@ func NewLocalResourceClient(server ResourceServer) ResourceClient {
grpcAuthInt := grpcutils.NewUnsafeAuthenticator(tracer)
for _, desc := range []*grpc.ServiceDesc{
&ResourceStore_ServiceDesc,
&ResourceIndex_ServiceDesc,
&ManagedObjectIndex_ServiceDesc,
&BlobStore_ServiceDesc,
&BulkStore_ServiceDesc,
&Diagnostics_ServiceDesc,
&resourcepb.ResourceStore_ServiceDesc,
&resourcepb.ResourceIndex_ServiceDesc,
&resourcepb.ManagedObjectIndex_ServiceDesc,
&resourcepb.BlobStore_ServiceDesc,
&resourcepb.BulkStore_ServiceDesc,
&resourcepb.Diagnostics_ServiceDesc,
} {
channel.RegisterService(
grpchan.InterceptServer(
@@ -87,12 +88,12 @@ func NewLocalResourceClient(server ResourceServer) ResourceClient {
cc := grpchan.InterceptClientConn(channel, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc),
ManagedObjectIndexClient: NewManagedObjectIndexClient(cc),
BulkStoreClient: NewBulkStoreClient(cc),
BlobStoreClient: NewBlobStoreClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
ResourceStoreClient: resourcepb.NewResourceStoreClient(cc),
ResourceIndexClient: resourcepb.NewResourceIndexClient(cc),
ManagedObjectIndexClient: resourcepb.NewManagedObjectIndexClient(cc),
BulkStoreClient: resourcepb.NewBulkStoreClient(cc),
BlobStoreClient: resourcepb.NewBlobStoreClient(cc),
DiagnosticsClient: resourcepb.NewDiagnosticsClient(cc),
}
}
@@ -129,12 +130,12 @@ func NewRemoteResourceClient(tracer trace.Tracer, conn grpc.ClientConnInterface,
cc := grpchan.InterceptClientConn(conn, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor)
return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc),
BlobStoreClient: NewBlobStoreClient(cc),
BulkStoreClient: NewBulkStoreClient(cc),
ManagedObjectIndexClient: NewManagedObjectIndexClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
ResourceStoreClient: resourcepb.NewResourceStoreClient(cc),
ResourceIndexClient: resourcepb.NewResourceIndexClient(cc),
BlobStoreClient: resourcepb.NewBlobStoreClient(cc),
BulkStoreClient: resourcepb.NewBulkStoreClient(cc),
ManagedObjectIndexClient: resourcepb.NewManagedObjectIndexClient(cc),
DiagnosticsClient: resourcepb.NewDiagnosticsClient(cc),
}, nil
}
+29 -28
View File
@@ -10,18 +10,19 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Convert raw resource bytes into an IndexableDocument
type DocumentBuilder interface {
// Convert raw bytes into an document that can be written
BuildDocument(ctx context.Context, key *ResourceKey, rv int64, value []byte) (*IndexableDocument, error)
BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*IndexableDocument, error)
}
// Registry of the searchable document fields
type SearchableDocumentFields interface {
Fields() []string
Field(name string) *ResourceTableColumnDefinition
Field(name string) *resourcepb.ResourceTableColumnDefinition
}
// Some kinds will require special processing for their namespace
@@ -51,7 +52,7 @@ type DocumentBuilderSupplier interface {
// Although public, this is *NOT* an end user interface
type IndexableDocument struct {
// The resource key
Key *ResourceKey `json:"key"`
Key *resourcepb.ResourceKey `json:"key"`
// The k8s name
Name string `json:"name,omitempty"`
@@ -165,7 +166,7 @@ func (m ResourceReferences) Less(i, j int) bool {
}
// Create a new indexable document based on a generic k8s resource
func NewIndexableDocument(key *ResourceKey, rv int64, obj utils.GrafanaMetaAccessor) *IndexableDocument {
func NewIndexableDocument(key *resourcepb.ResourceKey, rv int64, obj utils.GrafanaMetaAccessor) *IndexableDocument {
title := obj.FindTitle(key.Name)
if title == key.Name {
// TODO: something wrong with FindTitle
@@ -216,7 +217,7 @@ func StandardDocumentBuilder() DocumentBuilder {
type standardDocumentBuilder struct{}
func (s *standardDocumentBuilder) BuildDocument(ctx context.Context, key *ResourceKey, rv int64, value []byte) (*IndexableDocument, error) {
func (s *standardDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*IndexableDocument, error) {
tmp := &unstructured.Unstructured{}
err := tmp.UnmarshalJSON(value)
if err != nil {
@@ -238,7 +239,7 @@ type searchableDocumentFields struct {
}
// This requires unique names
func NewSearchableDocumentFields(columns []*ResourceTableColumnDefinition) (SearchableDocumentFields, error) {
func NewSearchableDocumentFields(columns []*resourcepb.ResourceTableColumnDefinition) (SearchableDocumentFields, error) {
f := &searchableDocumentFields{
names: make([]string, len(columns)),
fields: make(map[string]*resourceTableColumn),
@@ -261,7 +262,7 @@ func (x *searchableDocumentFields) Fields() []string {
return x.names
}
func (x *searchableDocumentFields) Field(name string) *ResourceTableColumnDefinition {
func (x *searchableDocumentFields) Field(name string) *resourcepb.ResourceTableColumnDefinition {
name = strings.TrimPrefix(name, SEARCH_FIELD_PREFIX)
f, ok := x.fields[name]
@@ -308,94 +309,94 @@ var standardSearchFields SearchableDocumentFields
func StandardSearchFields() SearchableDocumentFields {
standardSearchFieldsInit.Do(func() {
var err error
standardSearchFields, err = NewSearchableDocumentFields([]*ResourceTableColumnDefinition{
standardSearchFields, err = NewSearchableDocumentFields([]*resourcepb.ResourceTableColumnDefinition{
{
Name: SEARCH_FIELD_ID,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Unique Identifier. {namespace}/{group}/{resource}/{name}",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
NotNull: true,
},
},
{
Name: SEARCH_FIELD_GROUP_RESOURCE,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "The resource kind: {group}/{resource}",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
NotNull: true,
},
},
{
Name: SEARCH_FIELD_NAMESPACE,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Tenant isolation",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
NotNull: true,
},
},
{
Name: SEARCH_FIELD_NAME,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Kubernetes name. Unique identifier within a namespace+group+resource",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
NotNull: true,
},
},
{
Name: SEARCH_FIELD_TITLE,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Display name for the resource",
},
{
Name: SEARCH_FIELD_DESCRIPTION,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "An account of the resource.",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
FreeText: true,
},
},
{
Name: SEARCH_FIELD_TAGS,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
IsArray: true,
Description: "Unique tags",
Properties: &ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: SEARCH_FIELD_FOLDER,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Kubernetes name for the folder",
},
{
Name: SEARCH_FIELD_RV,
Type: ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "resource version",
},
{
Name: SEARCH_FIELD_CREATED,
Type: ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "created timestamp", // date?
},
{
Name: SEARCH_FIELD_EXPLAIN,
Type: ResourceTableColumnDefinition_OBJECT,
Type: resourcepb.ResourceTableColumnDefinition_OBJECT,
Description: "Explain why this result matches (depends on the engine)",
},
{
Name: SEARCH_FIELD_SCORE,
Type: ResourceTableColumnDefinition_DOUBLE,
Type: resourcepb.ResourceTableColumnDefinition_DOUBLE,
Description: "The search score",
},
{
Name: SEARCH_FIELD_LEGACY_ID,
Type: ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Deprecated legacy id of the resource",
},
{
Name: SEARCH_FIELD_MANAGER_KIND,
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
Description: "Type of manager, which is responsible for managing the resource",
},
})
@@ -8,6 +8,8 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestStandardDocumentBuilder(t *testing.T) {
@@ -16,7 +18,7 @@ func TestStandardDocumentBuilder(t *testing.T) {
body, err := os.ReadFile("testdata/playlist-resource.json")
require.NoError(t, err)
doc, err := builder.BuildDocument(ctx, &ResourceKey{
doc, err := builder.BuildDocument(ctx, &resourcepb.ResourceKey{
Namespace: "default",
Group: "playlists.grafana.app",
Resource: "playlists",
+13 -11
View File
@@ -10,6 +10,8 @@ import (
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
grpcstatus "google.golang.org/grpc/status"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Package-level errors.
@@ -29,18 +31,18 @@ var (
}
)
func NewBadRequestError(msg string) *ErrorResult {
return &ErrorResult{
func NewBadRequestError(msg string) *resourcepb.ErrorResult {
return &resourcepb.ErrorResult{
Message: msg,
Code: http.StatusBadRequest,
Reason: string(metav1.StatusReasonBadRequest),
}
}
func NewNotFoundError(key *ResourceKey) *ErrorResult {
return &ErrorResult{
func NewNotFoundError(key *resourcepb.ResourceKey) *resourcepb.ErrorResult {
return &resourcepb.ErrorResult{
Code: http.StatusNotFound,
Details: &ErrorDetails{
Details: &resourcepb.ErrorDetails{
Group: key.Group,
Kind: key.Resource, // yup, resource as kind same is true in apierrors.NewNotFound()
Name: key.Name,
@@ -49,7 +51,7 @@ func NewNotFoundError(key *ResourceKey) *ErrorResult {
}
// Convert golang errors to status result errors that can be returned to a client
func AsErrorResult(err error) *ErrorResult {
func AsErrorResult(err error) *resourcepb.ErrorResult {
if err == nil {
return nil
}
@@ -57,13 +59,13 @@ func AsErrorResult(err error) *ErrorResult {
var apistatus apierrors.APIStatus
if errors.As(err, &apistatus) {
s := apistatus.Status()
res := &ErrorResult{
res := &resourcepb.ErrorResult{
Message: s.Message,
Reason: string(s.Reason),
Code: s.Code,
}
if s.Details != nil {
res.Details = &ErrorDetails{
res.Details = &resourcepb.ErrorDetails{
Group: s.Details.Group,
Kind: s.Details.Kind,
Name: s.Details.Name,
@@ -71,7 +73,7 @@ func AsErrorResult(err error) *ErrorResult {
RetryAfterSeconds: s.Details.RetryAfterSeconds,
}
for _, c := range s.Details.Causes {
res.Details.Causes = append(res.Details.Causes, &ErrorCause{
res.Details.Causes = append(res.Details.Causes, &resourcepb.ErrorCause{
Reason: string(c.Type),
Message: c.Message,
Field: c.Field,
@@ -88,13 +90,13 @@ func AsErrorResult(err error) *ErrorResult {
code = runtime.HTTPStatusFromCode(st.Code())
}
return &ErrorResult{
return &resourcepb.ErrorResult{
Message: err.Error(),
Code: int32(code),
}
}
func GetError(res *ErrorResult) error {
func GetError(res *resourcepb.ErrorResult) error {
if res == nil {
return nil
}
+7 -6
View File
@@ -1,15 +1,16 @@
package resource
import (
context "context"
"context"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type WriteEvent struct {
Type WatchEvent_Type // ADDED, MODIFIED, DELETED
Key *ResourceKey // the request key
PreviousRV int64 // only for Update+Delete
Type resourcepb.WatchEvent_Type // ADDED, MODIFIED, DELETED
Key *resourcepb.ResourceKey // the request key
PreviousRV int64 // only for Update+Delete
// The json payload (without resourceVersion)
Value []byte
@@ -23,8 +24,8 @@ type WriteEvent struct {
// WrittenEvent is a WriteEvent reported with a resource version.
type WrittenEvent struct {
Type WatchEvent_Type
Key *ResourceKey
Type resourcepb.WatchEvent_Type
Key *resourcepb.ResourceKey
PreviousRV int64
// The json payload (without resourceVersion)
+4 -2
View File
@@ -7,6 +7,7 @@ replace (
github.com/grafana/grafana/apps/folder => ../../../../apps/folder
github.com/grafana/grafana/pkg/apimachinery => ../../../apimachinery
github.com/grafana/grafana/pkg/apiserver => ../../../apiserver
github.com/grafana/grafana/pkg/storage/unified/resourcepb => ../resourcepb
)
require (
@@ -21,6 +22,7 @@ require (
github.com/grafana/grafana/apps/dashboard v0.0.0-20250422074709-7c8433fbb2c2
github.com/grafana/grafana/apps/folder v0.0.0-20250422074709-7c8433fbb2c2
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250422074709-7c8433fbb2c2
github.com/grafana/grafana/pkg/storage/unified/resourcepb v0.0.0-00010101000000-000000000000
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
github.com/hashicorp/golang-lru/v2 v2.0.7
@@ -30,8 +32,7 @@ require (
go.opentelemetry.io/otel/trace v1.35.0
gocloud.dev v0.40.0
golang.org/x/sync v0.14.0
google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
google.golang.org/grpc v1.72.1
k8s.io/apimachinery v0.32.3
)
@@ -232,6 +233,7 @@ require (
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+2 -2
View File
@@ -850,8 +850,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+9 -7
View File
@@ -7,6 +7,8 @@ import (
grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"google.golang.org/grpc/health/grpc_health_v1"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Compile-time assertion
@@ -17,13 +19,13 @@ type HealthService interface {
grpcAuth.ServiceAuthFuncOverride
}
func ProvideHealthService(server DiagnosticsServer) (grpc_health_v1.HealthServer, error) {
func ProvideHealthService(server resourcepb.DiagnosticsServer) (grpc_health_v1.HealthServer, error) {
h := &healthServer{srv: server}
return h, nil
}
type healthServer struct {
srv DiagnosticsServer
srv resourcepb.DiagnosticsServer
}
// AuthFuncOverride for no auth for health service.
@@ -45,8 +47,8 @@ func (s *healthServer) List(ctx context.Context, req *grpc_health_v1.HealthListR
}, nil
}
func (s *healthServer) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
r, err := s.srv.IsHealthy(ctx, &HealthCheckRequest{})
func (s *healthServer) Check(ctx context.Context, _ *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
r, err := s.srv.IsHealthy(ctx, &resourcepb.HealthCheckRequest{})
if err != nil {
return nil, err
}
@@ -56,8 +58,8 @@ func (s *healthServer) Check(ctx context.Context, req *grpc_health_v1.HealthChec
}, nil
}
func (s *healthServer) Watch(req *grpc_health_v1.HealthCheckRequest, stream grpc_health_v1.Health_WatchServer) error {
h, err := s.srv.IsHealthy(stream.Context(), &HealthCheckRequest{})
func (s *healthServer) Watch(_ *grpc_health_v1.HealthCheckRequest, stream grpc_health_v1.Health_WatchServer) error {
h, err := s.srv.IsHealthy(stream.Context(), &resourcepb.HealthCheckRequest{})
if err != nil {
return err
}
@@ -77,7 +79,7 @@ func (s *healthServer) Watch(req *grpc_health_v1.HealthCheckRequest, stream grpc
select {
case <-ticker.C:
// get current health status
h, err := s.srv.IsHealthy(stream.Context(), &HealthCheckRequest{})
h, err := s.srv.IsHealthy(stream.Context(), &resourcepb.HealthCheckRequest{})
if err != nil {
return err
}
+11 -9
View File
@@ -3,7 +3,7 @@ package resource
import (
"context"
"errors"
sync "sync"
"sync"
"testing"
"time"
@@ -11,11 +11,13 @@ import (
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/health/grpc_health_v1"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestHealthCheck(t *testing.T) {
t.Run("will return serving response when healthy", func(t *testing.T) {
stub := &diag{healthResponse: HealthCheckResponse_SERVING}
stub := &diag{healthResponse: resourcepb.HealthCheckResponse_SERVING}
svc, err := ProvideHealthService(stub)
require.NoError(t, err)
@@ -27,7 +29,7 @@ func TestHealthCheck(t *testing.T) {
})
t.Run("will return not serving response when not healthy", func(t *testing.T) {
stub := &diag{healthResponse: HealthCheckResponse_NOT_SERVING}
stub := &diag{healthResponse: resourcepb.HealthCheckResponse_NOT_SERVING}
svc, err := ProvideHealthService(stub)
require.NoError(t, err)
@@ -41,7 +43,7 @@ func TestHealthCheck(t *testing.T) {
func TestHealthWatch(t *testing.T) {
t.Run("watch will return message when called", func(t *testing.T) {
stub := &diag{healthResponse: HealthCheckResponse_SERVING}
stub := &diag{healthResponse: resourcepb.HealthCheckResponse_SERVING}
svc, err := ProvideHealthService(stub)
require.NoError(t, err)
@@ -58,7 +60,7 @@ func TestHealthWatch(t *testing.T) {
})
t.Run("watch will return error when context cancelled", func(t *testing.T) {
stub := &diag{healthResponse: HealthCheckResponse_NOT_SERVING}
stub := &diag{healthResponse: resourcepb.HealthCheckResponse_NOT_SERVING}
svc, err := ProvideHealthService(stub)
require.NoError(t, err)
@@ -72,19 +74,19 @@ func TestHealthWatch(t *testing.T) {
})
}
var _ DiagnosticsServer = &diag{}
var _ resourcepb.DiagnosticsServer = &diag{}
type diag struct {
healthResponse HealthCheckResponse_ServingStatus
healthResponse resourcepb.HealthCheckResponse_ServingStatus
error error
}
func (s *diag) IsHealthy(ctx context.Context, req *HealthCheckRequest) (*HealthCheckResponse, error) {
func (s *diag) IsHealthy(ctx context.Context, req *resourcepb.HealthCheckRequest) (*resourcepb.HealthCheckResponse, error) {
if s.error != nil {
return nil, s.error
}
return &HealthCheckResponse{Status: s.healthResponse}, nil
return &resourcepb.HealthCheckResponse{Status: s.healthResponse}, nil
}
type fakeHealthWatchServer struct {
+7 -5
View File
@@ -3,9 +3,11 @@ package resource
import (
"fmt"
"strings"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func verifyRequestKey(key *ResourceKey) *ErrorResult {
func verifyRequestKey(key *resourcepb.ResourceKey) *resourcepb.ErrorResult {
if key == nil {
return NewBadRequestError("missing resource key")
}
@@ -18,7 +20,7 @@ func verifyRequestKey(key *ResourceKey) *ErrorResult {
return nil
}
func matchesQueryKey(query *ResourceKey, key *ResourceKey) bool {
func matchesQueryKey(query *resourcepb.ResourceKey, key *resourcepb.ResourceKey) bool {
if query.Group != key.Group {
return false
}
@@ -37,7 +39,7 @@ func matchesQueryKey(query *ResourceKey, key *ResourceKey) bool {
const clusterNamespace = "**cluster**"
// Convert the key to a search ID string
func (x *ResourceKey) SearchID() string {
func SearchID(x *resourcepb.ResourceKey) string {
var sb strings.Builder
if x.Namespace == "" {
sb.WriteString(clusterNamespace)
@@ -55,7 +57,7 @@ func (x *ResourceKey) SearchID() string {
return sb.String()
}
func (x *ResourceKey) ReadSearchID(v string) error {
func ReadSearchID(x *resourcepb.ResourceKey, v string) error {
parts := strings.Split(v, "/")
if len(parts) < 3 {
return fmt.Errorf("invalid search id (expecting 3 slashes)")
@@ -75,7 +77,7 @@ func (x *ResourceKey) ReadSearchID(v string) error {
}
// The namespace/group/resource
func (x *ResourceKey) NSGR() string {
func NSGR(x *resourcepb.ResourceKey) string {
var sb strings.Builder
if x.Namespace == "" {
sb.WriteString(clusterNamespace)
+11 -9
View File
@@ -4,15 +4,17 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestKeyMatching(t *testing.T) {
t.Run("key matching", func(t *testing.T) {
require.True(t, matchesQueryKey(&ResourceKey{
require.True(t, matchesQueryKey(&resourcepb.ResourceKey{
Group: "ggg",
Resource: "rrr",
Namespace: "ns",
}, &ResourceKey{
}, &resourcepb.ResourceKey{
Group: "ggg",
Resource: "rrr",
Namespace: "ns",
@@ -23,32 +25,32 @@ func TestKeyMatching(t *testing.T) {
func TestSearchIDKeys(t *testing.T) {
tests := []struct {
input string
expected *ResourceKey // nil error
expected *resourcepb.ResourceKey // nil error
}{
{input: "a"}, // error
{input: "default/group/resource/name",
expected: &ResourceKey{
expected: &resourcepb.ResourceKey{
Namespace: "default",
Group: "group",
Resource: "resource",
Name: "name",
}},
{input: "/group/resource/",
expected: &ResourceKey{
expected: &resourcepb.ResourceKey{
Namespace: "",
Group: "group",
Resource: "resource",
Name: "",
}},
{input: "default/group/resource",
expected: &ResourceKey{
expected: &resourcepb.ResourceKey{
Namespace: "default",
Group: "group",
Resource: "resource",
Name: "",
}},
{input: "**cluster**/group/resource/aaa", // cluster namespace
expected: &ResourceKey{
expected: &resourcepb.ResourceKey{
Namespace: "",
Group: "group",
Resource: "resource",
@@ -57,8 +59,8 @@ func TestSearchIDKeys(t *testing.T) {
}
for _, test := range tests {
tmp := &ResourceKey{}
err := tmp.ReadSearchID(test.input)
tmp := &resourcepb.ResourceKey{}
err := ReadSearchID(tmp, test.input)
if err == nil {
require.Equal(t, test.expected, tmp, test.input)
}
+8 -6
View File
@@ -4,19 +4,21 @@ import (
"fmt"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// MigrateVersionMatch handles backwards compatibility for ResourceVersionMatch
// by migrating from the deprecated version_match to version_match_v2.
// It returns an error if the version match is unknown.
func MigrateListRequestVersionMatch(req *ListRequest, logger logging.Logger) error {
if req.VersionMatch != nil && req.GetVersionMatchV2() == ResourceVersionMatchV2_UNKNOWN {
func MigrateListRequestVersionMatch(req *resourcepb.ListRequest, logger logging.Logger) error {
if req.VersionMatch != nil && req.GetVersionMatchV2() == resourcepb.ResourceVersionMatchV2_UNKNOWN {
switch req.GetVersionMatch() {
case ResourceVersionMatch_DEPRECATED_NotOlderThan:
case resourcepb.ResourceVersionMatch_DEPRECATED_NotOlderThan:
// This is not a typo. The old implementation actually did behave like Unset.
req.VersionMatchV2 = ResourceVersionMatchV2_Unset
case ResourceVersionMatch_DEPRECATED_Exact:
req.VersionMatchV2 = ResourceVersionMatchV2_Exact
req.VersionMatchV2 = resourcepb.ResourceVersionMatchV2_Unset
case resourcepb.ResourceVersionMatch_DEPRECATED_Exact:
req.VersionMatchV2 = resourcepb.ResourceVersionMatchV2_Exact
default:
return fmt.Errorf("unknown version match: %v", req.GetVersionMatch())
}
+8 -6
View File
@@ -2,11 +2,13 @@ package resource
import (
"context"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var (
_ DiagnosticsServer = (*noopService)(nil)
_ LifecycleHooks = (*noopService)(nil)
_ resourcepb.DiagnosticsServer = (*noopService)(nil)
_ LifecycleHooks = (*noopService)(nil)
)
// noopService is a helper implementation to simplify tests
@@ -24,12 +26,12 @@ func (n *noopService) Stop(context.Context) error {
}
// IsHealthy implements DiagnosticsServer
func (n *noopService) IsHealthy(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) {
return &HealthCheckResponse{
Status: HealthCheckResponse_SERVING,
func (n *noopService) IsHealthy(context.Context, *resourcepb.HealthCheckRequest) (*resourcepb.HealthCheckResponse, error) {
return &resourcepb.HealthCheckResponse{
Status: resourcepb.HealthCheckResponse_SERVING,
}, nil
}
func (n *noopService) Read(context.Context, *ReadRequest) (*ReadResponse, error) {
func (n *noopService) Read(context.Context, *resourcepb.ReadRequest) (*resourcepb.ReadResponse, error) {
return nil, ErrNotImplementedYet
}
+39 -37
View File
@@ -17,8 +17,10 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/authlib/types"
dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
const maxBatchSize = 1000
@@ -43,8 +45,8 @@ const (
type BulkIndexItem struct {
Action IndexAction
Key *ResourceKey // Only used for delete actions
Doc *IndexableDocument // Only used for index actions
Key *resourcepb.ResourceKey // Only used for delete actions
Doc *IndexableDocument // Only used for index actions
}
type BulkIndexRequest struct {
@@ -59,13 +61,13 @@ type ResourceIndex interface {
// Search within a namespaced resource
// When working with federated queries, the additional indexes will be passed in explicitly
Search(ctx context.Context, access types.AccessClient, req *ResourceSearchRequest, federate []ResourceIndex) (*ResourceSearchResponse, error)
Search(ctx context.Context, access types.AccessClient, req *resourcepb.ResourceSearchRequest, federate []ResourceIndex) (*resourcepb.ResourceSearchResponse, error)
// List within an response
ListManagedObjects(ctx context.Context, req *ListManagedObjectsRequest) (*ListManagedObjectsResponse, error)
ListManagedObjects(ctx context.Context, req *resourcepb.ListManagedObjectsRequest) (*resourcepb.ListManagedObjectsResponse, error)
// Counts the values in a repo
CountManagedObjects(ctx context.Context) ([]*CountManagedObjectsResponse_ResourceCount, error)
CountManagedObjects(ctx context.Context) ([]*resourcepb.CountManagedObjectsResponse_ResourceCount, error)
// Get the number of documents in the index
DocCount(ctx context.Context, folder string) (int64, error)
@@ -122,8 +124,8 @@ type searchSupport struct {
}
var (
_ ResourceIndexServer = (*searchSupport)(nil)
_ ManagedObjectIndexServer = (*searchSupport)(nil)
_ resourcepb.ResourceIndexServer = (*searchSupport)(nil)
_ resourcepb.ManagedObjectIndexServer = (*searchSupport)(nil)
)
func newSearchSupport(opts SearchOptions, storage StorageBackend, access types.AccessClient, blob BlobSupport, tracer trace.Tracer, indexMetrics *BleveIndexMetrics) (support *searchSupport, err error) {
@@ -166,14 +168,14 @@ func newSearchSupport(opts SearchOptions, storage StorageBackend, access types.A
return support, err
}
func (s *searchSupport) ListManagedObjects(ctx context.Context, req *ListManagedObjectsRequest) (*ListManagedObjectsResponse, error) {
func (s *searchSupport) ListManagedObjects(ctx context.Context, req *resourcepb.ListManagedObjectsRequest) (*resourcepb.ListManagedObjectsResponse, error) {
if req.NextPageToken != "" {
return &ListManagedObjectsResponse{
return &resourcepb.ListManagedObjectsResponse{
Error: NewBadRequestError("multiple pages not yet supported"),
}, nil
}
rsp := &ListManagedObjectsResponse{}
rsp := &resourcepb.ListManagedObjectsResponse{}
stats, err := s.storage.GetResourceStats(ctx, req.Namespace, 0)
if err != nil {
rsp.Error = AsErrorResult(err)
@@ -197,7 +199,7 @@ func (s *searchSupport) ListManagedObjects(ctx context.Context, req *ListManaged
return rsp, nil
}
if kind.NextPageToken != "" {
rsp.Error = &ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Message: "Multiple pages are not yet supported",
}
return rsp, nil
@@ -206,15 +208,15 @@ func (s *searchSupport) ListManagedObjects(ctx context.Context, req *ListManaged
}
// Sort based on path
slices.SortFunc(rsp.Items, func(a, b *ListManagedObjectsResponse_Item) int {
slices.SortFunc(rsp.Items, func(a, b *resourcepb.ListManagedObjectsResponse_Item) int {
return cmp.Compare(a.Path, b.Path)
})
return rsp, nil
}
func (s *searchSupport) CountManagedObjects(ctx context.Context, req *CountManagedObjectsRequest) (*CountManagedObjectsResponse, error) {
rsp := &CountManagedObjectsResponse{}
func (s *searchSupport) CountManagedObjects(ctx context.Context, req *resourcepb.CountManagedObjectsRequest) (*resourcepb.CountManagedObjectsResponse, error) {
rsp := &resourcepb.CountManagedObjectsResponse{}
stats, err := s.storage.GetResourceStats(ctx, req.Namespace, 0)
if err != nil {
rsp.Error = AsErrorResult(err)
@@ -249,7 +251,7 @@ func (s *searchSupport) CountManagedObjects(ctx context.Context, req *CountManag
}
// Sort based on manager/group/resource
slices.SortFunc(rsp.Items, func(a, b *CountManagedObjectsResponse_ResourceCount) int {
slices.SortFunc(rsp.Items, func(a, b *resourcepb.CountManagedObjectsResponse_ResourceCount) int {
return cmp.Or(
cmp.Compare(a.Kind, b.Kind),
cmp.Compare(a.Id, b.Id),
@@ -262,7 +264,7 @@ func (s *searchSupport) CountManagedObjects(ctx context.Context, req *CountManag
}
// Search implements ResourceIndexServer.
func (s *searchSupport) Search(ctx context.Context, req *ResourceSearchRequest) (*ResourceSearchResponse, error) {
func (s *searchSupport) Search(ctx context.Context, req *resourcepb.ResourceSearchRequest) (*resourcepb.ResourceSearchResponse, error) {
ctx, span := s.tracer.Start(ctx, tracingPrexfixSearch+"Search")
defer span.End()
@@ -273,7 +275,7 @@ func (s *searchSupport) Search(ctx context.Context, req *ResourceSearchRequest)
}
idx, err := s.getOrCreateIndex(ctx, nsr)
if err != nil {
return &ResourceSearchResponse{
return &resourcepb.ResourceSearchResponse{
Error: AsErrorResult(err),
}, nil
}
@@ -285,7 +287,7 @@ func (s *searchSupport) Search(ctx context.Context, req *ResourceSearchRequest)
nsr.Resource = f.Resource
federate[i], err = s.getOrCreateIndex(ctx, nsr)
if err != nil {
return &ResourceSearchResponse{
return &resourcepb.ResourceSearchResponse{
Error: AsErrorResult(err),
}, nil
}
@@ -295,17 +297,17 @@ func (s *searchSupport) Search(ctx context.Context, req *ResourceSearchRequest)
}
// GetStats implements ResourceServer.
func (s *searchSupport) GetStats(ctx context.Context, req *ResourceStatsRequest) (*ResourceStatsResponse, error) {
func (s *searchSupport) GetStats(ctx context.Context, req *resourcepb.ResourceStatsRequest) (*resourcepb.ResourceStatsResponse, error) {
if req.Namespace == "" {
return &ResourceStatsResponse{
return &resourcepb.ResourceStatsResponse{
Error: NewBadRequestError("missing namespace"),
}, nil
}
rsp := &ResourceStatsResponse{}
rsp := &resourcepb.ResourceStatsResponse{}
// Explicit list of kinds
if len(req.Kinds) > 0 {
rsp.Stats = make([]*ResourceStatsResponse_Stats, len(req.Kinds))
rsp.Stats = make([]*resourcepb.ResourceStatsResponse_Stats, len(req.Kinds))
for i, k := range req.Kinds {
parts := strings.SplitN(k, "/", 2)
index, err := s.getOrCreateIndex(ctx, NamespacedResource{
@@ -322,7 +324,7 @@ func (s *searchSupport) GetStats(ctx context.Context, req *ResourceStatsRequest)
rsp.Error = AsErrorResult(err)
return rsp, nil
}
rsp.Stats[i] = &ResourceStatsResponse_Stats{
rsp.Stats[i] = &resourcepb.ResourceStatsResponse_Stats{
Group: parts[0],
Resource: parts[1],
Count: count,
@@ -333,16 +335,16 @@ func (s *searchSupport) GetStats(ctx context.Context, req *ResourceStatsRequest)
stats, err := s.storage.GetResourceStats(ctx, req.Namespace, 0)
if err != nil {
return &ResourceStatsResponse{
return &resourcepb.ResourceStatsResponse{
Error: AsErrorResult(err),
}, nil
}
rsp.Stats = make([]*ResourceStatsResponse_Stats, len(stats))
rsp.Stats = make([]*resourcepb.ResourceStatsResponse_Stats, len(stats))
// When not filtered by folder or repository, we can use the results directly
if req.Folder == "" {
for i, stat := range stats {
rsp.Stats[i] = &ResourceStatsResponse_Stats{
rsp.Stats[i] = &resourcepb.ResourceStatsResponse_Stats{
Group: stat.Group,
Resource: stat.Resource,
Count: stat.Count,
@@ -366,7 +368,7 @@ func (s *searchSupport) GetStats(ctx context.Context, req *ResourceStatsRequest)
rsp.Error = AsErrorResult(err)
return rsp, nil
}
rsp.Stats[i] = &ResourceStatsResponse_Stats{
rsp.Stats[i] = &resourcepb.ResourceStatsResponse_Stats{
Group: stat.Group,
Resource: stat.Resource,
Count: count,
@@ -450,7 +452,7 @@ func (s *searchSupport) dispatchEvent(ctx context.Context, evt *WrittenEvent) {
)
switch evt.Type {
case WatchEvent_ADDED, WatchEvent_MODIFIED, WatchEvent_DELETED: // OK
case resourcepb.WatchEvent_ADDED, resourcepb.WatchEvent_MODIFIED, resourcepb.WatchEvent_DELETED: // OK
default:
s.log.Info("ignoring watch event", "type", evt.Type)
span.AddEvent("ignoring watch event", trace.WithAttributes(attribute.String("type", evt.Type.String())))
@@ -547,10 +549,10 @@ func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size
s.log.Debug("Building index", "resource", nsr.Resource, "size", size, "rv", rv)
index, err := s.search.BuildIndex(ctx, nsr, size, rv, fields, func(index ResourceIndex) (int64, error) {
rv, err = s.storage.ListIterator(ctx, &ListRequest{
rv, err = s.storage.ListIterator(ctx, &resourcepb.ListRequest{
Limit: 1000000000000, // big number
Options: &ListOptions{
Key: &ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: nsr.Group,
Resource: nsr.Resource,
Namespace: nsr.Namespace,
@@ -566,7 +568,7 @@ func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size
}
// Update the key name
key := &ResourceKey{
key := &resourcepb.ResourceKey{
Group: nsr.Group,
Resource: nsr.Resource,
Namespace: nsr.Namespace,
@@ -576,7 +578,7 @@ func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size
// Convert it to an indexable document
doc, err := builder.BuildDocument(ctx, key, iter.ResourceVersion(), iter.Value())
if err != nil {
s.log.Error("error building search document", "key", key.SearchID(), "err", err)
s.log.Error("error building search document", "key", SearchID(key), "err", err)
continue
}
@@ -703,19 +705,19 @@ func (s *builderCache) get(ctx context.Context, key NamespacedResource) (Documen
}
// AsResourceKey converts the given namespace and type to a search key
func AsResourceKey(ns string, t string) (*ResourceKey, error) {
func AsResourceKey(ns string, t string) (*resourcepb.ResourceKey, error) {
if ns == "" {
return nil, fmt.Errorf("missing namespace")
}
switch t {
case "folders", "folder":
return &ResourceKey{
return &resourcepb.ResourceKey{
Namespace: ns,
Group: folders.GROUP,
Resource: folders.RESOURCE,
}, nil
case "dashboards", "dashboard":
return &ResourceKey{
return &resourcepb.ResourceKey{
Namespace: ns,
Group: dashboardv1.GROUP,
Resource: dashboardv1.DASHBOARD_RESOURCE,
@@ -723,7 +725,7 @@ func AsResourceKey(ns string, t string) (*ResourceKey, error) {
// NOT really supported in the dashboard search UI, but useful for manual testing
case "playlist", "playlists":
return &ResourceKey{
return &resourcepb.ResourceKey{
Namespace: ns,
Group: "playlist.grafana.app",
Resource: "playlists",
+10 -5
View File
@@ -5,6 +5,8 @@ import (
"google.golang.org/grpc"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
type DualWriter interface {
@@ -12,7 +14,8 @@ type DualWriter interface {
ReadFromUnified(context.Context, schema.GroupResource) (bool, error)
}
func NewSearchClient(dual DualWriter, gr schema.GroupResource, unifiedClient ResourceIndexClient, legacyClient ResourceIndexClient) ResourceIndexClient {
func NewSearchClient(dual DualWriter, gr schema.GroupResource, unifiedClient resourcepb.ResourceIndexClient,
legacyClient resourcepb.ResourceIndexClient) resourcepb.ResourceIndexClient {
if dual.IsEnabled(gr) {
return &searchWrapper{
dual: dual,
@@ -32,11 +35,12 @@ type searchWrapper struct {
dual DualWriter
groupResource schema.GroupResource
unifiedClient ResourceIndexClient
legacyClient ResourceIndexClient
unifiedClient resourcepb.ResourceIndexClient
legacyClient resourcepb.ResourceIndexClient
}
func (s *searchWrapper) GetStats(ctx context.Context, in *ResourceStatsRequest, opts ...grpc.CallOption) (*ResourceStatsResponse, error) {
func (s *searchWrapper) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest,
opts ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) {
client := s.legacyClient
unified, err := s.dual.ReadFromUnified(ctx, s.groupResource)
if err != nil {
@@ -48,7 +52,8 @@ func (s *searchWrapper) GetStats(ctx context.Context, in *ResourceStatsRequest,
return client.GetStats(ctx, in, opts...)
}
func (s *searchWrapper) Search(ctx context.Context, in *ResourceSearchRequest, opts ...grpc.CallOption) (*ResourceSearchResponse, error) {
func (s *searchWrapper) Search(ctx context.Context, in *resourcepb.ResourceSearchRequest,
opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) {
client := s.legacyClient
unified, err := s.dual.ReadFromUnified(ctx, s.groupResource)
if err != nil {
+3 -1
View File
@@ -4,6 +4,8 @@ import (
"context"
"sync"
"time"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// indexQueueProcessor manages queue-based operations for a specific index
@@ -108,7 +110,7 @@ func (b *indexQueueProcessor) process(batch []*WrittenEvent) {
resp = append(resp, result)
item := &BulkIndexItem{}
if evt.Type == WatchEvent_DELETED {
if evt.Type == resourcepb.WatchEvent_DELETED {
item.Action = ActionDelete
item.Key = evt.Key
} else {
@@ -6,6 +6,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestNewIndexQueueProcessor(t *testing.T) {
@@ -32,11 +34,11 @@ func TestIndexQueueProcessor_SingleEvent(t *testing.T) {
processor := newIndexQueueProcessor(mockIndex, nsr, 10, mockBuilder, resChan)
// Test data
key := ResourceKey{Resource: "test", Name: "obj1", Namespace: "default"}
key := resourcepb.ResourceKey{Resource: "test", Name: "obj1", Namespace: "default"}
evt := &WrittenEvent{
Key: &key,
ResourceVersion: time.Now().UnixMicro(),
Type: WatchEvent_ADDED,
Type: resourcepb.WatchEvent_ADDED,
Value: []byte(`{"test": "data"}`),
}
@@ -70,15 +72,15 @@ func TestIndexQueueProcessor_BatchProcessing(t *testing.T) {
// Test data for two events
events := []*WrittenEvent{
{
Key: &ResourceKey{Resource: "test", Name: "obj1", Namespace: "default"},
Key: &resourcepb.ResourceKey{Resource: "test", Name: "obj1", Namespace: "default"},
ResourceVersion: time.Now().UnixMicro(),
Type: WatchEvent_ADDED,
Type: resourcepb.WatchEvent_ADDED,
Value: []byte(`{"test": "data1"}`),
},
{
Key: &ResourceKey{Resource: "test", Name: "obj2", Namespace: "default"},
Key: &resourcepb.ResourceKey{Resource: "test", Name: "obj2", Namespace: "default"},
ResourceVersion: time.Now().UnixMicro(),
Type: WatchEvent_DELETED,
Type: resourcepb.WatchEvent_DELETED,
},
}
@@ -117,9 +119,9 @@ func TestIndexQueueProcessor_BuildDocumentError(t *testing.T) {
processor := newIndexQueueProcessor(mockIndex, nsr, 10, mockBuilder, resChan)
evt := &WrittenEvent{
Key: &ResourceKey{Resource: "test", Name: "obj1", Namespace: "default"},
Key: &resourcepb.ResourceKey{Resource: "test", Name: "obj1", Namespace: "default"},
ResourceVersion: time.Now().UnixMicro(),
Type: WatchEvent_ADDED,
Type: resourcepb.WatchEvent_ADDED,
Value: []byte(`invalid json`),
}
@@ -151,9 +153,9 @@ func TestIndexQueueProcessor_BulkIndexError(t *testing.T) {
processor := newIndexQueueProcessor(mockIndex, nsr, 10, mockBuilder, resChan)
evt := &WrittenEvent{
Key: &ResourceKey{Resource: "test", Name: "obj1", Namespace: "default"},
Key: &resourcepb.ResourceKey{Resource: "test", Name: "obj1", Namespace: "default"},
ResourceVersion: time.Now().UnixMicro(),
Type: WatchEvent_ADDED,
Type: resourcepb.WatchEvent_ADDED,
Value: []byte(`{"test": "data"}`),
}
+9 -7
View File
@@ -5,6 +5,8 @@ import (
"github.com/grafana/authlib/types"
"github.com/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var _ ResourceIndex = &MockResourceIndex{}
@@ -19,14 +21,14 @@ func (m *MockResourceIndex) BulkIndex(req *BulkIndexRequest) error {
return args.Error(0)
}
func (m *MockResourceIndex) Search(ctx context.Context, access types.AccessClient, req *ResourceSearchRequest, federate []ResourceIndex) (*ResourceSearchResponse, error) {
func (m *MockResourceIndex) Search(ctx context.Context, access types.AccessClient, req *resourcepb.ResourceSearchRequest, federate []ResourceIndex) (*resourcepb.ResourceSearchResponse, error) {
args := m.Called(ctx, access, req, federate)
return args.Get(0).(*ResourceSearchResponse), args.Error(1)
return args.Get(0).(*resourcepb.ResourceSearchResponse), args.Error(1)
}
func (m *MockResourceIndex) CountManagedObjects(ctx context.Context) ([]*CountManagedObjectsResponse_ResourceCount, error) {
func (m *MockResourceIndex) CountManagedObjects(ctx context.Context) ([]*resourcepb.CountManagedObjectsResponse_ResourceCount, error) {
args := m.Called(ctx)
return args.Get(0).([]*CountManagedObjectsResponse_ResourceCount), args.Error(1)
return args.Get(0).([]*resourcepb.CountManagedObjectsResponse_ResourceCount), args.Error(1)
}
func (m *MockResourceIndex) DocCount(ctx context.Context, folder string) (int64, error) {
@@ -34,9 +36,9 @@ func (m *MockResourceIndex) DocCount(ctx context.Context, folder string) (int64,
return args.Get(0).(int64), args.Error(1)
}
func (m *MockResourceIndex) ListManagedObjects(ctx context.Context, req *ListManagedObjectsRequest) (*ListManagedObjectsResponse, error) {
func (m *MockResourceIndex) ListManagedObjects(ctx context.Context, req *resourcepb.ListManagedObjectsRequest) (*resourcepb.ListManagedObjectsResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(*ListManagedObjectsResponse), args.Error(1)
return args.Get(0).(*resourcepb.ListManagedObjectsResponse), args.Error(1)
}
var _ DocumentBuilder = &MockDocumentBuilder{}
@@ -45,7 +47,7 @@ type MockDocumentBuilder struct {
mock.Mock
}
func (m *MockDocumentBuilder) BuildDocument(ctx context.Context, key *ResourceKey, resourceVersion int64, value []byte) (*IndexableDocument, error) {
func (m *MockDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, resourceVersion int64, value []byte) (*IndexableDocument, error) {
args := m.Called(ctx, key, resourceVersion, value)
if args.Get(0) == nil {
return nil, args.Error(1)
+87 -85
View File
@@ -24,17 +24,19 @@ import (
"github.com/grafana/dskit/ring"
ringclient "github.com/grafana/dskit/ring/client"
userutils "github.com/grafana/dskit/user"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// ResourceServer implements all gRPC services
type ResourceServer interface {
ResourceStoreServer
BulkStoreServer
ResourceIndexServer
ManagedObjectIndexServer
BlobStoreServer
DiagnosticsServer
resourcepb.ResourceStoreServer
resourcepb.BulkStoreServer
resourcepb.ResourceIndexServer
resourcepb.ManagedObjectIndexServer
resourcepb.BlobStoreServer
resourcepb.DiagnosticsServer
}
type ListIterator interface {
@@ -73,7 +75,7 @@ type ListIterator interface {
type BackendReadResponse struct {
// Metadata
Key *ResourceKey
Key *resourcepb.ResourceKey
Folder string
// GUID that is used internally
@@ -83,7 +85,7 @@ type BackendReadResponse struct {
// The properties
Value []byte
// Error details
Error *ErrorResult
Error *resourcepb.ErrorResult
}
// The StorageBackend is an internal abstraction that supports interacting with
@@ -96,14 +98,14 @@ type StorageBackend interface {
WriteEvent(context.Context, WriteEvent) (int64, error)
// Read a resource from storage optionally at an explicit version
ReadResource(context.Context, *ReadRequest) *BackendReadResponse
ReadResource(context.Context, *resourcepb.ReadRequest) *BackendReadResponse
// When the ResourceServer executes a List request, this iterator will
// query the backend for potential results. All results will be
// checked against the kubernetes requirements before finally returning
// results. The list options can be used to improve performance
// but are the the final answer.
ListIterator(context.Context, *ListRequest, func(ListIterator) error) (int64, error)
ListIterator(context.Context, *resourcepb.ListRequest, func(ListIterator) error) (int64, error)
// Get all events from the store
// For HA setups, this will be more events than the local WriteEvent above!
@@ -128,11 +130,11 @@ type BlobSupport interface {
// Get the raw blob bytes and metadata -- limited to protobuf message size
// For larger payloads, we should use presigned URLs to upload from the client
PutResourceBlob(context.Context, *PutBlobRequest) (*PutBlobResponse, error)
PutResourceBlob(context.Context, *resourcepb.PutBlobRequest) (*resourcepb.PutBlobResponse, error)
// Get blob contents. When possible, this will return a signed URL
// For large payloads, signed URLs are required to avoid protobuf message size limits
GetResourceBlob(ctx context.Context, resource *ResourceKey, info *utils.BlobInfo, mustProxy bool) (*GetBlobResponse, error)
GetResourceBlob(ctx context.Context, resource *resourcepb.ResourceKey, info *utils.BlobInfo, mustProxy bool) (*resourcepb.GetBlobResponse, error)
// TODO? List+Delete? This is for admin access
}
@@ -177,7 +179,7 @@ type ResourceServerOptions struct {
Search SearchOptions
// Diagnostics
Diagnostics DiagnosticsServer
Diagnostics resourcepb.DiagnosticsServer
// Check if a user has access to write folders
// When this is nil, no resources can have folders configured
@@ -298,7 +300,7 @@ type server struct {
backend StorageBackend
blob BlobSupport
search *searchSupport
diagnostics DiagnosticsServer
diagnostics resourcepb.DiagnosticsServer
access claims.AccessClient
writeHooks WriteAccessHooks
lifecycle LifecycleHooks
@@ -431,7 +433,7 @@ func (s *server) Stop(ctx context.Context) error {
}
// Old value indicates an update -- otherwise a create
func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *ResourceKey, value, oldValue []byte) (*WriteEvent, *ErrorResult) {
func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *resourcepb.ResourceKey, value, oldValue []byte) (*WriteEvent, *resourcepb.ErrorResult) {
tmp := &unstructured.Unstructured{}
err := tmp.UnmarshalJSON(value)
if err != nil {
@@ -470,9 +472,9 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour
}
if oldValue == nil {
event.Type = WatchEvent_ADDED
event.Type = resourcepb.WatchEvent_ADDED
} else {
event.Type = WatchEvent_MODIFIED
event.Type = resourcepb.WatchEvent_MODIFIED
temp := &unstructured.Unstructured{}
err = temp.UnmarshalJSON(oldValue)
@@ -530,7 +532,7 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour
Namespace: key.Namespace,
}
if event.Type == WatchEvent_MODIFIED {
if event.Type == resourcepb.WatchEvent_MODIFIED {
check.Verb = utils.VerbUpdate
check.Name = key.Name
}
@@ -541,7 +543,7 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour
return nil, AsErrorResult(err)
}
if !a.Allowed {
return nil, &ErrorResult{
return nil, &resourcepb.ErrorResult{
Code: http.StatusForbidden,
}
}
@@ -559,13 +561,13 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour
// isFolderMove determines if an event represents a resource being moved between folders
func (s *server) isFolderMove(event *WriteEvent) bool {
return event.Type == WatchEvent_MODIFIED &&
return event.Type == resourcepb.WatchEvent_MODIFIED &&
event.ObjectOld != nil &&
event.ObjectOld.GetFolder() != event.Object.GetFolder()
}
// checkFolderMovePermissions handles permission checks when a resource is being moved between folders
func (s *server) checkFolderMovePermissions(ctx context.Context, user claims.AuthInfo, key *ResourceKey, oldFolder, newFolder string) *ErrorResult {
func (s *server) checkFolderMovePermissions(ctx context.Context, user claims.AuthInfo, key *resourcepb.ResourceKey, oldFolder, newFolder string) *resourcepb.ErrorResult {
// First check if user can update the resource in the original folder
updateCheck := claims.CheckRequest{
Verb: utils.VerbUpdate,
@@ -581,7 +583,7 @@ func (s *server) checkFolderMovePermissions(ctx context.Context, user claims.Aut
return AsErrorResult(err)
}
if !a.Allowed {
return &ErrorResult{
return &resourcepb.ErrorResult{
Code: http.StatusForbidden,
Message: "not allowed to update resource in the source folder",
}
@@ -601,7 +603,7 @@ func (s *server) checkFolderMovePermissions(ctx context.Context, user claims.Aut
return AsErrorResult(err)
}
if !a.Allowed {
return &ErrorResult{
return &resourcepb.ErrorResult{
Code: http.StatusForbidden,
Message: "not allowed to create resource in the destination folder",
}
@@ -610,14 +612,14 @@ func (s *server) checkFolderMovePermissions(ctx context.Context, user claims.Aut
return nil
}
func (s *server) Create(ctx context.Context, req *CreateRequest) (*CreateResponse, error) {
func (s *server) Create(ctx context.Context, req *resourcepb.CreateRequest) (*resourcepb.CreateResponse, error) {
ctx, span := s.tracer.Start(ctx, "storage_server.Create")
defer span.End()
rsp := &CreateResponse{}
rsp := &resourcepb.CreateResponse{}
user, ok := claims.AuthInfoFrom(ctx)
if !ok || user == nil {
rsp.Error = &ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Message: "no user found in context",
Code: http.StatusUnauthorized,
}
@@ -641,14 +643,14 @@ func (s *server) Create(ctx context.Context, req *CreateRequest) (*CreateRespons
return rsp, nil
}
func (s *server) Update(ctx context.Context, req *UpdateRequest) (*UpdateResponse, error) {
func (s *server) Update(ctx context.Context, req *resourcepb.UpdateRequest) (*resourcepb.UpdateResponse, error) {
ctx, span := s.tracer.Start(ctx, "storage_server.Update")
defer span.End()
rsp := &UpdateResponse{}
rsp := &resourcepb.UpdateResponse{}
user, ok := claims.AuthInfoFrom(ctx)
if !ok || user == nil {
rsp.Error = &ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Message: "no user found in context",
Code: http.StatusUnauthorized,
}
@@ -659,7 +661,7 @@ func (s *server) Update(ctx context.Context, req *UpdateRequest) (*UpdateRespons
return rsp, nil
}
latest := s.backend.ReadResource(ctx, &ReadRequest{
latest := s.backend.ReadResource(ctx, &resourcepb.ReadRequest{
Key: req.Key,
})
if latest.Error != nil {
@@ -680,7 +682,7 @@ func (s *server) Update(ctx context.Context, req *UpdateRequest) (*UpdateRespons
return rsp, nil
}
event.Type = WatchEvent_MODIFIED
event.Type = resourcepb.WatchEvent_MODIFIED
event.PreviousRV = latest.ResourceVersion
var err error
@@ -691,24 +693,24 @@ func (s *server) Update(ctx context.Context, req *UpdateRequest) (*UpdateRespons
return rsp, nil
}
func (s *server) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) {
func (s *server) Delete(ctx context.Context, req *resourcepb.DeleteRequest) (*resourcepb.DeleteResponse, error) {
ctx, span := s.tracer.Start(ctx, "storage_server.Delete")
defer span.End()
rsp := &DeleteResponse{}
rsp := &resourcepb.DeleteResponse{}
if req.ResourceVersion < 0 {
return nil, apierrors.NewBadRequest("update must include the previous version")
}
user, ok := claims.AuthInfoFrom(ctx)
if !ok || user == nil {
rsp.Error = &ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Message: "no user found in context",
Code: http.StatusUnauthorized,
}
return rsp, nil
}
latest := s.backend.ReadResource(ctx, &ReadRequest{
latest := s.backend.ReadResource(ctx, &resourcepb.ReadRequest{
Key: req.Key,
})
if latest.Error != nil {
@@ -733,7 +735,7 @@ func (s *server) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRespons
return rsp, nil
}
if !access.Allowed {
rsp.Error = &ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Code: http.StatusForbidden,
}
return rsp, nil
@@ -742,7 +744,7 @@ func (s *server) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRespons
now := metav1.NewTime(time.UnixMilli(s.now()))
event := WriteEvent{
Key: req.Key,
Type: WatchEvent_DELETED,
Type: resourcepb.WatchEvent_DELETED,
PreviousRV: latest.ResourceVersion,
}
requester, ok := claims.AuthInfoFrom(ctx)
@@ -779,11 +781,11 @@ func (s *server) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRespons
return rsp, nil
}
func (s *server) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, error) {
func (s *server) Read(ctx context.Context, req *resourcepb.ReadRequest) (*resourcepb.ReadResponse, error) {
user, ok := claims.AuthInfoFrom(ctx)
if !ok || user == nil {
return &ReadResponse{
Error: &ErrorResult{
return &resourcepb.ReadResponse{
Error: &resourcepb.ErrorResult{
Message: "no user found in context",
Code: http.StatusUnauthorized,
}}, nil
@@ -794,12 +796,12 @@ func (s *server) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, err
// return &ReadResponse{Status: status}, nil
// }
if req.Key.Resource == "" {
return &ReadResponse{Error: NewBadRequestError("missing resource")}, nil
return &resourcepb.ReadResponse{Error: NewBadRequestError("missing resource")}, nil
}
rsp := s.backend.ReadResource(ctx, req)
if rsp.Error != nil && rsp.Error.Code == http.StatusNotFound {
return &ReadResponse{Error: rsp.Error}, nil
return &resourcepb.ReadResponse{Error: rsp.Error}, nil
}
a, err := s.access.Check(ctx, user, claims.CheckRequest{
@@ -811,29 +813,29 @@ func (s *server) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, err
Folder: rsp.Folder,
})
if err != nil {
return &ReadResponse{Error: AsErrorResult(err)}, nil
return &resourcepb.ReadResponse{Error: AsErrorResult(err)}, nil
}
if !a.Allowed {
return &ReadResponse{
Error: &ErrorResult{
return &resourcepb.ReadResponse{
Error: &resourcepb.ErrorResult{
Code: http.StatusForbidden,
}}, nil
}
return &ReadResponse{
return &resourcepb.ReadResponse{
ResourceVersion: rsp.ResourceVersion,
Value: rsp.Value,
Error: rsp.Error,
}, nil
}
func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, error) {
func (s *server) List(ctx context.Context, req *resourcepb.ListRequest) (*resourcepb.ListResponse, error) {
ctx, span := s.tracer.Start(ctx, "storage_server.List")
defer span.End()
// The history + trash queries do not yet support additional filters
if req.Source != ListRequest_STORE {
if req.Source != resourcepb.ListRequest_STORE {
if len(req.Options.Fields) > 0 || len(req.Options.Labels) > 0 {
return &ListResponse{
return &resourcepb.ListResponse{
Error: NewBadRequestError("unexpected field/label selector for history query"),
}, nil
}
@@ -841,8 +843,8 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err
user, ok := claims.AuthInfoFrom(ctx)
if !ok || user == nil {
return &ListResponse{
Error: &ErrorResult{
return &resourcepb.ListResponse{
Error: &resourcepb.ErrorResult{
Message: "no user found in context",
Code: http.StatusUnauthorized,
}}, nil
@@ -851,7 +853,7 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err
// Do not allow label query for trash/history
for _, v := range req.Options.Labels {
if v.Key == utils.LabelKeyGetHistory || v.Key == utils.LabelKeyGetTrash {
return &ListResponse{Error: NewBadRequestError("history and trash must be requested as source")}, nil
return &resourcepb.ListResponse{Error: NewBadRequestError("history and trash must be requested as source")}, nil
}
}
@@ -860,7 +862,7 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err
}
maxPageBytes := 1024 * 1024 * 2 // 2mb/page
pageBytes := 0
rsp := &ListResponse{}
rsp := &resourcepb.ListResponse{}
key := req.Options.Key
checker, err := s.access.Compile(ctx, user, claims.ListRequest{
@@ -870,10 +872,10 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err
Verb: utils.VerbGet,
})
if err != nil {
return &ListResponse{Error: AsErrorResult(err)}, nil
return &resourcepb.ListResponse{Error: AsErrorResult(err)}, nil
}
if checker == nil {
return &ListResponse{Error: &ErrorResult{
return &resourcepb.ListResponse{Error: &resourcepb.ErrorResult{
Code: http.StatusForbidden,
}}, nil
}
@@ -884,7 +886,7 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err
return err
}
item := &ResourceWrapper{
item := &resourcepb.ResourceWrapper{
ResourceVersion: iter.ResourceVersion(),
Value: iter.Value(),
}
@@ -897,7 +899,7 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err
rsp.Items = append(rsp.Items, item)
if len(rsp.Items) >= int(req.Limit) || pageBytes >= maxPageBytes {
t := iter.ContinueToken()
if req.Source == ListRequest_HISTORY || req.Source == ListRequest_TRASH {
if req.Source == resourcepb.ListRequest_HISTORY || req.Source == resourcepb.ListRequest_TRASH {
// For history lists, we need to use the current RV in the continue token
// to ensure consistent pagination. The order depends on VersionMatch:
// - NotOlderThan: ascending order (oldest to newest)
@@ -918,7 +920,7 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err
}
if rv < 1 {
rsp.Error = &ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Code: http.StatusInternalServerError,
Message: fmt.Sprintf("invalid resource version for list: %v", rv),
}
@@ -957,7 +959,7 @@ func (s *server) initWatcher() error {
}
//nolint:gocyclo
func (s *server) Watch(req *WatchRequest, srv ResourceStore_WatchServer) error {
func (s *server) Watch(req *resourcepb.WatchRequest, srv resourcepb.ResourceStore_WatchServer) error {
ctx := srv.Context()
user, ok := claims.AuthInfoFrom(ctx)
@@ -999,14 +1001,14 @@ func (s *server) Watch(req *WatchRequest, srv ResourceStore_WatchServer) error {
var initialEventsRV int64 // resource version coming from the initial events
if req.SendInitialEvents {
// Backfill the stream by adding every existing entities.
initialEventsRV, err = s.backend.ListIterator(ctx, &ListRequest{Options: req.Options}, func(iter ListIterator) error {
initialEventsRV, err = s.backend.ListIterator(ctx, &resourcepb.ListRequest{Options: req.Options}, func(iter ListIterator) error {
for iter.Next() {
if err := iter.Error(); err != nil {
return err
}
if err := srv.Send(&WatchEvent{
Type: WatchEvent_ADDED,
Resource: &WatchEvent_Resource{
if err := srv.Send(&resourcepb.WatchEvent{
Type: resourcepb.WatchEvent_ADDED,
Resource: &resourcepb.WatchEvent_Resource{
Value: iter.Value(),
Version: iter.ResourceVersion(),
},
@@ -1021,9 +1023,9 @@ func (s *server) Watch(req *WatchRequest, srv ResourceStore_WatchServer) error {
}
}
if req.SendInitialEvents && req.AllowWatchBookmarks {
if err := srv.Send(&WatchEvent{
Type: WatchEvent_BOOKMARK,
Resource: &WatchEvent_Resource{
if err := srv.Send(&resourcepb.WatchEvent{
Type: resourcepb.WatchEvent_BOOKMARK,
Resource: &resourcepb.WatchEvent_Resource{
Version: initialEventsRV,
},
}); err != nil {
@@ -1058,19 +1060,19 @@ func (s *server) Watch(req *WatchRequest, srv ResourceStore_WatchServer) error {
value := event.Value
// remove the delete marker stored in the value for deleted objects
if event.Type == WatchEvent_DELETED {
if event.Type == resourcepb.WatchEvent_DELETED {
value = []byte{}
}
resp := &WatchEvent{
resp := &resourcepb.WatchEvent{
Timestamp: event.Timestamp,
Type: event.Type,
Resource: &WatchEvent_Resource{
Resource: &resourcepb.WatchEvent_Resource{
Value: value,
Version: event.ResourceVersion,
},
}
if event.PreviousRV > 0 {
prevObj, err := s.Read(ctx, &ReadRequest{Key: event.Key, ResourceVersion: event.PreviousRV})
prevObj, err := s.Read(ctx, &resourcepb.ReadRequest{Key: event.Key, ResourceVersion: event.PreviousRV})
if err != nil {
// This scenario should never happen, but if it does, we should log it and continue
// sending the event without the previous object. The client will decide what to do.
@@ -1080,7 +1082,7 @@ func (s *server) Watch(req *WatchRequest, srv ResourceStore_WatchServer) error {
s.log.Error("resource version mismatch", "key", event.Key, "resource_version", event.PreviousRV, "actual", prevObj.ResourceVersion)
return fmt.Errorf("resource version mismatch")
}
resp.Previous = &WatchEvent_Resource{
resp.Previous = &resourcepb.WatchEvent_Resource{
Value: prevObj.Value,
Version: prevObj.ResourceVersion,
}
@@ -1102,7 +1104,7 @@ func (s *server) Watch(req *WatchRequest, srv ResourceStore_WatchServer) error {
}
}
func (s *server) Search(ctx context.Context, req *ResourceSearchRequest) (*ResourceSearchResponse, error) {
func (s *server) Search(ctx context.Context, req *resourcepb.ResourceSearchRequest) (*resourcepb.ResourceSearchResponse, error) {
if s.search == nil {
return nil, fmt.Errorf("search index not configured")
}
@@ -1118,7 +1120,7 @@ func (s *server) Search(ctx context.Context, req *ResourceSearchRequest) (*Resou
}
// GetStats implements ResourceServer.
func (s *server) GetStats(ctx context.Context, req *ResourceStatsRequest) (*ResourceStatsResponse, error) {
func (s *server) GetStats(ctx context.Context, req *resourcepb.ResourceStatsRequest) (*resourcepb.ResourceStatsResponse, error) {
if err := s.Init(ctx); err != nil {
return nil, err
}
@@ -1132,7 +1134,7 @@ func (s *server) GetStats(ctx context.Context, req *ResourceStatsRequest) (*Reso
if s.search == nil {
// If the backend implements "GetStats", we can use it
srv, ok := s.backend.(ResourceIndexServer)
srv, ok := s.backend.(resourcepb.ResourceIndexServer)
if ok {
return srv.GetStats(ctx, req)
}
@@ -1141,23 +1143,23 @@ func (s *server) GetStats(ctx context.Context, req *ResourceStatsRequest) (*Reso
return s.search.GetStats(ctx, req)
}
func (s *server) ListManagedObjects(ctx context.Context, req *ListManagedObjectsRequest) (*ListManagedObjectsResponse, error) {
func (s *server) ListManagedObjects(ctx context.Context, req *resourcepb.ListManagedObjectsRequest) (*resourcepb.ListManagedObjectsResponse, error) {
return s.search.ListManagedObjects(ctx, req)
}
func (s *server) CountManagedObjects(ctx context.Context, req *CountManagedObjectsRequest) (*CountManagedObjectsResponse, error) {
func (s *server) CountManagedObjects(ctx context.Context, req *resourcepb.CountManagedObjectsRequest) (*resourcepb.CountManagedObjectsResponse, error) {
return s.search.CountManagedObjects(ctx, req)
}
// IsHealthy implements ResourceServer.
func (s *server) IsHealthy(ctx context.Context, req *HealthCheckRequest) (*HealthCheckResponse, error) {
func (s *server) IsHealthy(ctx context.Context, req *resourcepb.HealthCheckRequest) (*resourcepb.HealthCheckResponse, error) {
return s.diagnostics.IsHealthy(ctx, req)
}
// GetBlob implements BlobStore.
func (s *server) PutBlob(ctx context.Context, req *PutBlobRequest) (*PutBlobResponse, error) {
func (s *server) PutBlob(ctx context.Context, req *resourcepb.PutBlobRequest) (*resourcepb.PutBlobResponse, error) {
if s.blob == nil {
return &PutBlobResponse{Error: &ErrorResult{
return &resourcepb.PutBlobResponse{Error: &resourcepb.ErrorResult{
Message: "blob store not configured",
Code: http.StatusNotImplemented,
}}, nil
@@ -1170,12 +1172,12 @@ func (s *server) PutBlob(ctx context.Context, req *PutBlobRequest) (*PutBlobResp
return rsp, nil
}
func (s *server) getPartialObject(ctx context.Context, key *ResourceKey, rv int64) (utils.GrafanaMetaAccessor, *ErrorResult) {
func (s *server) getPartialObject(ctx context.Context, key *resourcepb.ResourceKey, rv int64) (utils.GrafanaMetaAccessor, *resourcepb.ErrorResult) {
if r := verifyRequestKey(key); r != nil {
return nil, r
}
rsp := s.backend.ReadResource(ctx, &ReadRequest{
rsp := s.backend.ReadResource(ctx, &resourcepb.ReadRequest{
Key: key,
ResourceVersion: rv,
})
@@ -1196,9 +1198,9 @@ func (s *server) getPartialObject(ctx context.Context, key *ResourceKey, rv int6
}
// GetBlob implements BlobStore.
func (s *server) GetBlob(ctx context.Context, req *GetBlobRequest) (*GetBlobResponse, error) {
func (s *server) GetBlob(ctx context.Context, req *resourcepb.GetBlobRequest) (*resourcepb.GetBlobResponse, error) {
if s.blob == nil {
return &GetBlobResponse{Error: &ErrorResult{
return &resourcepb.GetBlobResponse{Error: &resourcepb.ErrorResult{
Message: "blob store not configured",
Code: http.StatusNotImplemented,
}}, nil
@@ -1209,12 +1211,12 @@ func (s *server) GetBlob(ctx context.Context, req *GetBlobRequest) (*GetBlobResp
// The linked blob is stored in the resource metadata attributes
obj, status := s.getPartialObject(ctx, req.Resource, req.ResourceVersion)
if status != nil {
return &GetBlobResponse{Error: status}, nil
return &resourcepb.GetBlobResponse{Error: status}, nil
}
info = obj.GetBlob()
if info == nil || info.UID == "" {
return &GetBlobResponse{Error: &ErrorResult{
return &resourcepb.GetBlobResponse{Error: &resourcepb.ErrorResult{
Message: "Resource does not have a linked blob",
Code: 404,
}}, nil
+21 -19
View File
@@ -15,8 +15,10 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestSimpleServer(t *testing.T) {
@@ -78,7 +80,7 @@ func TestSimpleServer(t *testing.T) {
}
}`)
key := &ResourceKey{
key := &resourcepb.ResourceKey{
Group: "playlist.grafana.app",
Resource: "rrrr", // can be anything :(
Namespace: "default",
@@ -86,8 +88,8 @@ func TestSimpleServer(t *testing.T) {
}
// Should be empty when we start
all, err := server.List(ctx, &ListRequest{Options: &ListOptions{
Key: &ResourceKey{
all, err := server.List(ctx, &resourcepb.ListRequest{Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: key.Group,
Resource: key.Resource,
},
@@ -96,12 +98,12 @@ func TestSimpleServer(t *testing.T) {
require.Len(t, all.Items, 0)
// should return 404 if not found
found, err := server.Read(ctx, &ReadRequest{Key: key})
found, err := server.Read(ctx, &resourcepb.ReadRequest{Key: key})
require.NoError(t, err)
require.NotNil(t, found.Error)
require.Equal(t, int32(http.StatusNotFound), found.Error.Code)
created, err := server.Create(ctx, &CreateRequest{
created, err := server.Create(ctx, &resourcepb.CreateRequest{
Value: raw,
Key: key,
})
@@ -110,7 +112,7 @@ func TestSimpleServer(t *testing.T) {
require.True(t, created.ResourceVersion > 0)
// The key does not include resource version
found, err = server.Read(ctx, &ReadRequest{Key: key})
found, err = server.Read(ctx, &resourcepb.ReadRequest{Key: key})
require.NoError(t, err)
require.Nil(t, found.Error)
require.Equal(t, created.ResourceVersion, found.ResourceVersion)
@@ -131,7 +133,7 @@ func TestSimpleServer(t *testing.T) {
})
raw, err = json.Marshal(tmp)
require.NoError(t, err)
updated, err := server.Update(ctx, &UpdateRequest{
updated, err := server.Update(ctx, &resourcepb.UpdateRequest{
Key: key,
Value: raw,
ResourceVersion: created.ResourceVersion})
@@ -142,7 +144,7 @@ func TestSimpleServer(t *testing.T) {
obj.SetLabels(nil)
raw, err = json.Marshal(tmp)
require.NoError(t, err)
updated, err = server.Update(ctx, &UpdateRequest{
updated, err = server.Update(ctx, &resourcepb.UpdateRequest{
Key: key,
Value: raw,
ResourceVersion: created.ResourceVersion})
@@ -151,13 +153,13 @@ func TestSimpleServer(t *testing.T) {
require.True(t, updated.ResourceVersion > created.ResourceVersion)
// We should still get the latest
found, err = server.Read(ctx, &ReadRequest{Key: key})
found, err = server.Read(ctx, &resourcepb.ReadRequest{Key: key})
require.NoError(t, err)
require.Nil(t, found.Error)
require.Equal(t, updated.ResourceVersion, found.ResourceVersion)
all, err = server.List(ctx, &ListRequest{Options: &ListOptions{
Key: &ResourceKey{
all, err = server.List(ctx, &resourcepb.ListRequest{Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: key.Group,
Resource: key.Resource,
},
@@ -166,19 +168,19 @@ func TestSimpleServer(t *testing.T) {
require.Len(t, all.Items, 1)
require.Equal(t, updated.ResourceVersion, all.Items[0].ResourceVersion)
deleted, err := server.Delete(ctx, &DeleteRequest{Key: key, ResourceVersion: updated.ResourceVersion})
deleted, err := server.Delete(ctx, &resourcepb.DeleteRequest{Key: key, ResourceVersion: updated.ResourceVersion})
require.NoError(t, err)
require.True(t, deleted.ResourceVersion > updated.ResourceVersion)
// We should get not found status when trying to read the latest value
found, err = server.Read(ctx, &ReadRequest{Key: key})
found, err = server.Read(ctx, &resourcepb.ReadRequest{Key: key})
require.NoError(t, err)
require.NotNil(t, found.Error)
require.Equal(t, int32(404), found.Error.Code)
// And the deleted value should not be in the results
all, err = server.List(ctx, &ListRequest{Options: &ListOptions{
Key: &ResourceKey{
all, err = server.List(ctx, &resourcepb.ListRequest{Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: key.Group,
Resource: key.Resource,
},
@@ -213,14 +215,14 @@ func TestSimpleServer(t *testing.T) {
}
}`)
key := &ResourceKey{
key := &resourcepb.ResourceKey{
Group: "playlist.grafana.app",
Resource: "rrrr", // can be anything :(
Namespace: "default",
Name: "fdgsv37qslr0ga",
}
created, err := server.Create(ctx, &CreateRequest{
created, err := server.Create(ctx, &resourcepb.CreateRequest{
Value: raw,
Key: key,
})
@@ -228,13 +230,13 @@ func TestSimpleServer(t *testing.T) {
// Update should return an ErrOptimisticLockingFailed the second time
_, err = server.Update(ctx, &UpdateRequest{
_, err = server.Update(ctx, &resourcepb.UpdateRequest{
Key: key,
Value: raw,
ResourceVersion: created.ResourceVersion})
require.NoError(t, err)
_, err = server.Update(ctx, &UpdateRequest{
_, err = server.Update(ctx, &resourcepb.UpdateRequest{
Key: key,
Value: raw,
ResourceVersion: created.ResourceVersion})
+37 -35
View File
@@ -8,7 +8,7 @@ import (
"fmt"
"io"
"os"
reflect "reflect"
"reflect"
"strconv"
"testing"
"time"
@@ -20,10 +20,12 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Convert the the protobuf model into k8s (will decode each value)
func (x *ResourceTable) ToK8s() (metav1.Table, error) {
func toK8s(x *resourcepb.ResourceTable) (metav1.Table, error) {
table := metav1.Table{
ListMeta: metav1.ListMeta{
Continue: x.NextPageToken,
@@ -103,7 +105,7 @@ func (x *ResourceTable) ToK8s() (metav1.Table, error) {
}
type TableBuilder struct {
ResourceTable
resourcepb.ResourceTable
lookup map[string]*resourceTableColumn
@@ -113,9 +115,9 @@ type TableBuilder struct {
type ResourceColumnEncoder = func(v any) ([]byte, error)
func NewTableBuilder(cols []*ResourceTableColumnDefinition) (*TableBuilder, error) {
func NewTableBuilder(cols []*resourcepb.ResourceTableColumnDefinition) (*TableBuilder, error) {
table := &TableBuilder{
ResourceTable: ResourceTable{
ResourceTable: resourcepb.ResourceTable{
Columns: cols,
},
@@ -147,8 +149,8 @@ func (x *TableBuilder) Encoders() []ResourceColumnEncoder {
return encoders
}
func (x *TableBuilder) AddRow(key *ResourceKey, rv int64, vals map[string]any) error {
row := &ResourceTableRow{
func (x *TableBuilder) AddRow(key *resourcepb.ResourceKey, rv int64, vals map[string]any) error {
row := &resourcepb.ResourceTableRow{
Key: key,
ResourceVersion: rv,
Cells: make([][]byte, len(x.Columns)),
@@ -171,7 +173,7 @@ func (x *TableBuilder) AddRow(key *ResourceKey, rv int64, vals map[string]any) e
}
type resourceTableColumn struct {
def *ResourceTableColumnDefinition
def *resourcepb.ResourceTableColumnDefinition
index int
// Used for array indexing
@@ -183,7 +185,7 @@ type resourceTableColumn struct {
}
// helper to decode a cell value
func DecodeCell(columnDef *ResourceTableColumnDefinition, index int, cellVal []byte) (any, error) {
func DecodeCell(columnDef *resourcepb.ResourceTableColumnDefinition, index int, cellVal []byte) (any, error) {
col, err := newResourceTableColumn(columnDef, index)
if err != nil {
return nil, err
@@ -196,48 +198,48 @@ func DecodeCell(columnDef *ResourceTableColumnDefinition, index int, cellVal []b
}
// nolint:gocyclo
func newResourceTableColumn(def *ResourceTableColumnDefinition, index int) (*resourceTableColumn, error) {
func newResourceTableColumn(def *resourcepb.ResourceTableColumnDefinition, index int) (*resourceTableColumn, error) {
col := &resourceTableColumn{def: def, index: index}
// Initially ignore the array property, we wil wrap that at the end
switch def.Type {
case ResourceTableColumnDefinition_UNKNOWN_TYPE:
case resourcepb.ResourceTableColumnDefinition_UNKNOWN_TYPE:
return nil, fmt.Errorf("unknown column type")
case ResourceTableColumnDefinition_STRING:
case resourcepb.ResourceTableColumnDefinition_STRING:
col.OpenAPIType = "string"
col.reader = func(iter *jsoniter.Iterator) (any, error) {
return iter.ReadString()
}
case ResourceTableColumnDefinition_BOOLEAN:
case resourcepb.ResourceTableColumnDefinition_BOOLEAN:
col.OpenAPIType = "boolean"
col.reader = func(iter *jsoniter.Iterator) (any, error) {
return iter.ReadBool()
}
case ResourceTableColumnDefinition_INT32:
case resourcepb.ResourceTableColumnDefinition_INT32:
col.OpenAPIType = "number"
col.OpenAPIFormat = "int32"
col.reader = func(iter *jsoniter.Iterator) (any, error) {
return iter.ReadInt32()
}
case ResourceTableColumnDefinition_INT64:
case resourcepb.ResourceTableColumnDefinition_INT64:
col.OpenAPIType = "number"
col.OpenAPIFormat = "int64"
col.reader = func(iter *jsoniter.Iterator) (any, error) {
return iter.ReadInt64()
}
case ResourceTableColumnDefinition_DOUBLE:
case resourcepb.ResourceTableColumnDefinition_DOUBLE:
col.OpenAPIType = "number"
col.OpenAPIFormat = "double"
col.reader = func(iter *jsoniter.Iterator) (any, error) {
return iter.ReadFloat64()
}
case ResourceTableColumnDefinition_FLOAT:
case resourcepb.ResourceTableColumnDefinition_FLOAT:
col.OpenAPIType = "number"
col.OpenAPIFormat = "float"
col.reader = func(iter *jsoniter.Iterator) (any, error) {
@@ -245,10 +247,10 @@ func newResourceTableColumn(def *ResourceTableColumnDefinition, index int) (*res
}
// Encode everything we can -- the lower conversion can happen later?
case ResourceTableColumnDefinition_DATE, ResourceTableColumnDefinition_DATE_TIME:
case resourcepb.ResourceTableColumnDefinition_DATE, resourcepb.ResourceTableColumnDefinition_DATE_TIME:
col.OpenAPIType = "string"
col.OpenAPIFormat = "date"
if def.Type == ResourceTableColumnDefinition_DATE_TIME {
if def.Type == resourcepb.ResourceTableColumnDefinition_DATE_TIME {
col.OpenAPIFormat = "date_time"
}
col.writer = func(v any, stream *jsoniter.Stream) error {
@@ -287,7 +289,7 @@ func newResourceTableColumn(def *ResourceTableColumnDefinition, index int) (*res
}
}
case ResourceTableColumnDefinition_BINARY:
case resourcepb.ResourceTableColumnDefinition_BINARY:
col.OpenAPIType = "binary"
col.writer = func(v any, stream *jsoniter.Stream) error {
b, ok := v.([]byte)
@@ -306,7 +308,7 @@ func newResourceTableColumn(def *ResourceTableColumnDefinition, index int) (*res
return base64.StdEncoding.DecodeString(str)
}
case ResourceTableColumnDefinition_OBJECT:
case resourcepb.ResourceTableColumnDefinition_OBJECT:
col.OpenAPIType = "string"
col.OpenAPIFormat = "json"
@@ -337,7 +339,7 @@ func (x *resourceTableColumn) Encode(v any) ([]byte, error) {
// Arrays will always use JSON formatting
if !x.def.IsArray {
switch x.def.Type {
case ResourceTableColumnDefinition_STRING:
case resourcepb.ResourceTableColumnDefinition_STRING:
{
s, ok := v.(string)
if !ok {
@@ -345,7 +347,7 @@ func (x *resourceTableColumn) Encode(v any) ([]byte, error) {
}
return []byte(s), nil
}
case ResourceTableColumnDefinition_BINARY:
case resourcepb.ResourceTableColumnDefinition_BINARY:
{
s, ok := v.([]byte)
if !ok {
@@ -353,7 +355,7 @@ func (x *resourceTableColumn) Encode(v any) ([]byte, error) {
}
return s, nil
}
case ResourceTableColumnDefinition_BOOLEAN:
case resourcepb.ResourceTableColumnDefinition_BOOLEAN:
{
b, ok := v.(bool)
if !ok {
@@ -375,7 +377,7 @@ func (x *resourceTableColumn) Encode(v any) ([]byte, error) {
}
return []byte{0}, nil
}
case ResourceTableColumnDefinition_DATE_TIME, ResourceTableColumnDefinition_DATE:
case resourcepb.ResourceTableColumnDefinition_DATE_TIME, resourcepb.ResourceTableColumnDefinition_DATE:
{
f, ok := v.(time.Time)
if !ok {
@@ -397,7 +399,7 @@ func (x *resourceTableColumn) Encode(v any) ([]byte, error) {
err := binary.Write(&buf, binary.BigEndian, ts)
return buf.Bytes(), err
}
case ResourceTableColumnDefinition_DOUBLE:
case resourcepb.ResourceTableColumnDefinition_DOUBLE:
{
f, ok := v.(float64)
if !ok {
@@ -420,7 +422,7 @@ func (x *resourceTableColumn) Encode(v any) ([]byte, error) {
err := binary.Write(&buf, binary.BigEndian, f)
return buf.Bytes(), err
}
case ResourceTableColumnDefinition_INT64:
case resourcepb.ResourceTableColumnDefinition_INT64:
{
f, ok := v.(int64)
if !ok {
@@ -516,15 +518,15 @@ func (x *resourceTableColumn) Decode(buff []byte) (any, error) {
}
if !x.def.IsArray {
switch x.def.Type {
case ResourceTableColumnDefinition_STRING:
case resourcepb.ResourceTableColumnDefinition_STRING:
return string(buff), nil
case ResourceTableColumnDefinition_BINARY:
case resourcepb.ResourceTableColumnDefinition_BINARY:
return buff, nil
case ResourceTableColumnDefinition_BOOLEAN:
case resourcepb.ResourceTableColumnDefinition_BOOLEAN:
if len(buff) == 1 {
return buff[0] != 0, nil
}
case ResourceTableColumnDefinition_DOUBLE:
case resourcepb.ResourceTableColumnDefinition_DOUBLE:
{
var f float64
count, err := binary.Decode(buff, binary.BigEndian, &f)
@@ -532,7 +534,7 @@ func (x *resourceTableColumn) Decode(buff []byte) (any, error) {
return f, nil
}
}
case ResourceTableColumnDefinition_INT64:
case resourcepb.ResourceTableColumnDefinition_INT64:
{
var f int64
count, err := binary.Decode(buff, binary.BigEndian, &f)
@@ -540,7 +542,7 @@ func (x *resourceTableColumn) Decode(buff []byte) (any, error) {
return f, nil
}
}
case ResourceTableColumnDefinition_DATE_TIME, ResourceTableColumnDefinition_DATE:
case resourcepb.ResourceTableColumnDefinition_DATE_TIME, resourcepb.ResourceTableColumnDefinition_DATE:
{
var f int64
count, err := binary.Decode(buff, binary.BigEndian, &f)
@@ -587,10 +589,10 @@ func (x *resourceTableColumn) Decode(buff []byte) (any, error) {
}
// AssertTableSnapshot will match a ResourceTable vs the saved value
func AssertTableSnapshot(t *testing.T, path string, table *ResourceTable) {
func AssertTableSnapshot(t *testing.T, path string, table *resourcepb.ResourceTable) {
t.Helper()
k8sTable, err := table.ToK8s()
k8sTable, err := toK8s(table)
require.NoError(t, err, "unable to create table response", path)
actual, err := json.MarshalIndent(k8sTable, "", " ")
require.NoError(t, err, "unable to write table json", path)
+54 -52
View File
@@ -10,27 +10,29 @@ import (
"time"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestTableFormat(t *testing.T) {
columns := []*ResourceTableColumnDefinition{
columns := []*resourcepb.ResourceTableColumnDefinition{
{
Name: "title",
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: "stats.count",
Type: ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
},
{
Name: "number",
Type: ResourceTableColumnDefinition_DOUBLE,
Type: resourcepb.ResourceTableColumnDefinition_DOUBLE,
Description: "float64 value",
},
{
Name: "tags",
Type: ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
IsArray: true,
},
}
@@ -39,7 +41,7 @@ func TestTableFormat(t *testing.T) {
builder, err := NewTableBuilder(columns)
require.NoError(t, err)
err = builder.AddRow(&ResourceKey{
err = builder.AddRow(&resourcepb.ResourceKey{
Namespace: "default",
Group: "ggg",
Resource: "xyz", // does not have a home in table!
@@ -51,7 +53,7 @@ func TestTableFormat(t *testing.T) {
})
require.NoError(t, err)
err = builder.AddRow(&ResourceKey{
err = builder.AddRow(&resourcepb.ResourceKey{
Namespace: "default",
Group: "ggg",
Resource: "xyz", // does not have a home in table!
@@ -70,7 +72,7 @@ func TestTableFormat(t *testing.T) {
func TestColumnEncoding(t *testing.T) {
tests := []struct {
// The table definition
def *ResourceTableColumnDefinition
def *resourcepb.ResourceTableColumnDefinition
// Passed to the encode function
input any
@@ -88,101 +90,101 @@ func TestColumnEncoding(t *testing.T) {
output_err error
}{
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_STRING,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
input: "aaa", // expects output to match
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_STRING,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_STRING,
IsArray: true,
},
input: "bbb",
output: []any{"bbb"},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_INT64,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_INT64,
},
input: 12345,
output: int64(12345),
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_INT64,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_INT64,
IsArray: true,
},
input: 12345,
output: []any{int64(12345)},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_DOUBLE,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_DOUBLE,
},
input: 12345,
output: float64(12345),
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_DOUBLE,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_DOUBLE,
IsArray: true,
},
input: 12345,
output: []any{float64(12345)},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_BOOLEAN,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_BOOLEAN,
},
input: true,
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_BOOLEAN,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_BOOLEAN,
IsArray: true,
},
input: []any{true, false, true},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_FLOAT,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_FLOAT,
},
input: 23.4,
output: float32(23.4),
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_FLOAT,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_FLOAT,
IsArray: true,
},
input: 23.4,
output: []any{float32(23.4)},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_INT32,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_INT32,
},
input: 56,
output: int32(56),
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_INT32,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_INT32,
IsArray: true,
},
input: 56,
output: []any{int32(56)},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_DATE_TIME,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_DATE_TIME,
},
input: time.UnixMilli(946674000000).UTC(),
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_DATE_TIME,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_DATE_TIME,
IsArray: true,
},
input: time.UnixMilli(946674000000).UTC(),
@@ -191,14 +193,14 @@ func TestColumnEncoding(t *testing.T) {
},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_DATE,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_DATE,
},
input: time.UnixMilli(946674000000).UTC(),
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_DATE,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_DATE,
IsArray: true,
},
input: time.UnixMilli(946674000000).UTC(),
@@ -207,14 +209,14 @@ func TestColumnEncoding(t *testing.T) {
},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_BINARY,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_BINARY,
},
input: []byte{1, 2, 3, 4},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_BINARY,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_BINARY,
IsArray: true,
},
input: []any{
@@ -222,16 +224,16 @@ func TestColumnEncoding(t *testing.T) {
},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_OBJECT,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_OBJECT,
},
input: map[string]any{
"hello": "world",
},
},
{
def: &ResourceTableColumnDefinition{
Type: ResourceTableColumnDefinition_OBJECT,
def: &resourcepb.ResourceTableColumnDefinition{
Type: resourcepb.ResourceTableColumnDefinition_OBJECT,
IsArray: true,
},
input: map[string]any{
@@ -246,8 +248,8 @@ func TestColumnEncoding(t *testing.T) {
}
// Keep track of the types that have tests
testedTypes := make(map[ResourceTableColumnDefinition_ColumnType]bool)
testedArrays := make(map[ResourceTableColumnDefinition_ColumnType]bool)
testedTypes := make(map[resourcepb.ResourceTableColumnDefinition_ColumnType]bool)
testedArrays := make(map[resourcepb.ResourceTableColumnDefinition_ColumnType]bool)
for _, test := range tests {
var sb strings.Builder
if test.def.IsArray {
@@ -301,7 +303,7 @@ func TestColumnEncoding(t *testing.T) {
missingArrays := []string{}
// Make sure we have at least one test for each type
for i := ResourceTableColumnDefinition_STRING; i <= ResourceTableColumnDefinition_OBJECT; i++ {
for i := resourcepb.ResourceTableColumnDefinition_STRING; i <= resourcepb.ResourceTableColumnDefinition_OBJECT; i++ {
if !testedTypes[i] {
missingTypes = append(missingTypes, i.String())
}
@@ -316,7 +318,7 @@ func TestColumnEncoding(t *testing.T) {
}
func TestDecodeCell(t *testing.T) {
colDef := &ResourceTableColumnDefinition{Type: ResourceTableColumnDefinition_INT64}
colDef := &resourcepb.ResourceTableColumnDefinition{Type: resourcepb.ResourceTableColumnDefinition_INT64}
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, int64(123))
require.NoError(t, err)
+3 -1
View File
@@ -2,12 +2,14 @@ package resource
import (
"regexp"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
var validNameCharPattern = `a-zA-Z0-9:\-\_\.`
var validNamePattern = regexp.MustCompile(`^[` + validNameCharPattern + `]*$`).MatchString
func validateName(name string) *ErrorResult {
func validateName(name string) *resourcepb.ErrorResult {
if len(name) == 0 {
return NewBadRequestError("name is too short")
}
+520
View File
@@ -0,0 +1,520 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.5
// protoc (unknown)
// source: blob.proto
package resourcepb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type PutBlobRequest_Method int32
const (
// Use the inline raw []byte
PutBlobRequest_GRPC PutBlobRequest_Method = 0
// Get a signed URL and PUT the value
PutBlobRequest_HTTP PutBlobRequest_Method = 1
)
// Enum value maps for PutBlobRequest_Method.
var (
PutBlobRequest_Method_name = map[int32]string{
0: "GRPC",
1: "HTTP",
}
PutBlobRequest_Method_value = map[string]int32{
"GRPC": 0,
"HTTP": 1,
}
)
func (x PutBlobRequest_Method) Enum() *PutBlobRequest_Method {
p := new(PutBlobRequest_Method)
*p = x
return p
}
func (x PutBlobRequest_Method) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PutBlobRequest_Method) Descriptor() protoreflect.EnumDescriptor {
return file_blob_proto_enumTypes[0].Descriptor()
}
func (PutBlobRequest_Method) Type() protoreflect.EnumType {
return &file_blob_proto_enumTypes[0]
}
func (x PutBlobRequest_Method) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PutBlobRequest_Method.Descriptor instead.
func (PutBlobRequest_Method) EnumDescriptor() ([]byte, []int) {
return file_blob_proto_rawDescGZIP(), []int{0, 0}
}
type PutBlobRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The resource that will use this blob
// NOTE: the name may not yet exist, but group+resource are required
Resource *ResourceKey `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"`
// How to upload
Method PutBlobRequest_Method `protobuf:"varint,2,opt,name=method,proto3,enum=resource.PutBlobRequest_Method" json:"method,omitempty"`
// Content type header
ContentType string `protobuf:"bytes,3,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
// Raw value to write
// Not valid when method == HTTP
Value []byte `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PutBlobRequest) Reset() {
*x = PutBlobRequest{}
mi := &file_blob_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PutBlobRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PutBlobRequest) ProtoMessage() {}
func (x *PutBlobRequest) ProtoReflect() protoreflect.Message {
mi := &file_blob_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PutBlobRequest.ProtoReflect.Descriptor instead.
func (*PutBlobRequest) Descriptor() ([]byte, []int) {
return file_blob_proto_rawDescGZIP(), []int{0}
}
func (x *PutBlobRequest) GetResource() *ResourceKey {
if x != nil {
return x.Resource
}
return nil
}
func (x *PutBlobRequest) GetMethod() PutBlobRequest_Method {
if x != nil {
return x.Method
}
return PutBlobRequest_GRPC
}
func (x *PutBlobRequest) GetContentType() string {
if x != nil {
return x.ContentType
}
return ""
}
func (x *PutBlobRequest) GetValue() []byte {
if x != nil {
return x.Value
}
return nil
}
type PutBlobResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Error details
Error *ErrorResult `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
// The blob uid. This must be saved into the resource to support access
Uid string `protobuf:"bytes,2,opt,name=uid,proto3" json:"uid,omitempty"`
// The URL where this value can be PUT
Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"`
// Size of the uploaded blob
Size int64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"`
// Content hash used for an etag
Hash string `protobuf:"bytes,5,opt,name=hash,proto3" json:"hash,omitempty"`
// Validated mimetype (from content_type)
MimeType string `protobuf:"bytes,6,opt,name=mime_type,json=mimeType,proto3" json:"mime_type,omitempty"`
// Validated charset (from content_type)
Charset string `protobuf:"bytes,7,opt,name=charset,proto3" json:"charset,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PutBlobResponse) Reset() {
*x = PutBlobResponse{}
mi := &file_blob_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PutBlobResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PutBlobResponse) ProtoMessage() {}
func (x *PutBlobResponse) ProtoReflect() protoreflect.Message {
mi := &file_blob_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PutBlobResponse.ProtoReflect.Descriptor instead.
func (*PutBlobResponse) Descriptor() ([]byte, []int) {
return file_blob_proto_rawDescGZIP(), []int{1}
}
func (x *PutBlobResponse) GetError() *ErrorResult {
if x != nil {
return x.Error
}
return nil
}
func (x *PutBlobResponse) GetUid() string {
if x != nil {
return x.Uid
}
return ""
}
func (x *PutBlobResponse) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
func (x *PutBlobResponse) GetSize() int64 {
if x != nil {
return x.Size
}
return 0
}
func (x *PutBlobResponse) GetHash() string {
if x != nil {
return x.Hash
}
return ""
}
func (x *PutBlobResponse) GetMimeType() string {
if x != nil {
return x.MimeType
}
return ""
}
func (x *PutBlobResponse) GetCharset() string {
if x != nil {
return x.Charset
}
return ""
}
type GetBlobRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Resource *ResourceKey `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"`
// The new resource version
ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"`
// Do not return a pre-signed URL (when possible)
MustProxyBytes bool `protobuf:"varint,3,opt,name=must_proxy_bytes,json=mustProxyBytes,proto3" json:"must_proxy_bytes,omitempty"`
// The blob UID -- when empty, the value is loaded from annotations in the matching resource
Uid string `protobuf:"bytes,4,opt,name=uid,proto3" json:"uid,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetBlobRequest) Reset() {
*x = GetBlobRequest{}
mi := &file_blob_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetBlobRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlobRequest) ProtoMessage() {}
func (x *GetBlobRequest) ProtoReflect() protoreflect.Message {
mi := &file_blob_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlobRequest.ProtoReflect.Descriptor instead.
func (*GetBlobRequest) Descriptor() ([]byte, []int) {
return file_blob_proto_rawDescGZIP(), []int{2}
}
func (x *GetBlobRequest) GetResource() *ResourceKey {
if x != nil {
return x.Resource
}
return nil
}
func (x *GetBlobRequest) GetResourceVersion() int64 {
if x != nil {
return x.ResourceVersion
}
return 0
}
func (x *GetBlobRequest) GetMustProxyBytes() bool {
if x != nil {
return x.MustProxyBytes
}
return false
}
func (x *GetBlobRequest) GetUid() string {
if x != nil {
return x.Uid
}
return ""
}
type GetBlobResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Error details
Error *ErrorResult `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
// (optional) When possible, the system will return a presigned URL
// that can be used to actually read the full blob+metadata
// When this is set, neither info nor value will be set
Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
// Content type
ContentType string `protobuf:"bytes,3,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
// The raw object value
Value []byte `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetBlobResponse) Reset() {
*x = GetBlobResponse{}
mi := &file_blob_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetBlobResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlobResponse) ProtoMessage() {}
func (x *GetBlobResponse) ProtoReflect() protoreflect.Message {
mi := &file_blob_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlobResponse.ProtoReflect.Descriptor instead.
func (*GetBlobResponse) Descriptor() ([]byte, []int) {
return file_blob_proto_rawDescGZIP(), []int{3}
}
func (x *GetBlobResponse) GetError() *ErrorResult {
if x != nil {
return x.Error
}
return nil
}
func (x *GetBlobResponse) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
func (x *GetBlobResponse) GetContentType() string {
if x != nil {
return x.ContentType
}
return ""
}
func (x *GetBlobResponse) GetValue() []byte {
if x != nil {
return x.Value
}
return nil
}
var File_blob_proto protoreflect.FileDescriptor
var file_blob_proto_rawDesc = string([]byte{
0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x1a, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd3, 0x01, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x42, 0x6c,
0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b,
0x65, 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d,
0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e,
0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1c,
0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x08, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43,
0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x22, 0xc1, 0x01, 0x0a,
0x0f, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72,
0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a,
0x03, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12,
0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72,
0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52,
0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d,
0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69,
0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74,
0x22, 0xaa, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x75, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f,
0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6d, 0x75, 0x73,
0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75,
0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x22, 0x89, 0x01,
0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f,
0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10,
0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c,
0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54,
0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x8b, 0x01, 0x0a, 0x09, 0x42, 0x6c,
0x6f, 0x62, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x50, 0x75, 0x74, 0x42, 0x6c,
0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75,
0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x6c,
0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65,
0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72,
0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
file_blob_proto_rawDescOnce sync.Once
file_blob_proto_rawDescData []byte
)
func file_blob_proto_rawDescGZIP() []byte {
file_blob_proto_rawDescOnce.Do(func() {
file_blob_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_blob_proto_rawDesc), len(file_blob_proto_rawDesc)))
})
return file_blob_proto_rawDescData
}
var file_blob_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_blob_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_blob_proto_goTypes = []any{
(PutBlobRequest_Method)(0), // 0: resource.PutBlobRequest.Method
(*PutBlobRequest)(nil), // 1: resource.PutBlobRequest
(*PutBlobResponse)(nil), // 2: resource.PutBlobResponse
(*GetBlobRequest)(nil), // 3: resource.GetBlobRequest
(*GetBlobResponse)(nil), // 4: resource.GetBlobResponse
(*ResourceKey)(nil), // 5: resource.ResourceKey
(*ErrorResult)(nil), // 6: resource.ErrorResult
}
var file_blob_proto_depIdxs = []int32{
5, // 0: resource.PutBlobRequest.resource:type_name -> resource.ResourceKey
0, // 1: resource.PutBlobRequest.method:type_name -> resource.PutBlobRequest.Method
6, // 2: resource.PutBlobResponse.error:type_name -> resource.ErrorResult
5, // 3: resource.GetBlobRequest.resource:type_name -> resource.ResourceKey
6, // 4: resource.GetBlobResponse.error:type_name -> resource.ErrorResult
1, // 5: resource.BlobStore.PutBlob:input_type -> resource.PutBlobRequest
3, // 6: resource.BlobStore.GetBlob:input_type -> resource.GetBlobRequest
2, // 7: resource.BlobStore.PutBlob:output_type -> resource.PutBlobResponse
4, // 8: resource.BlobStore.GetBlob:output_type -> resource.GetBlobResponse
7, // [7:9] is the sub-list for method output_type
5, // [5:7] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_blob_proto_init() }
func file_blob_proto_init() {
if File_blob_proto != nil {
return
}
file_resource_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_blob_proto_rawDesc), len(file_blob_proto_rawDesc)),
NumEnums: 1,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_blob_proto_goTypes,
DependencyIndexes: file_blob_proto_depIdxs,
EnumInfos: file_blob_proto_enumTypes,
MessageInfos: file_blob_proto_msgTypes,
}.Build()
File_blob_proto = out.File
file_blob_proto_goTypes = nil
file_blob_proto_depIdxs = nil
}
@@ -0,0 +1,152 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.4.0
// - protoc (unknown)
// source: blob.proto
package resourcepb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.62.0 or later.
const _ = grpc.SupportPackageIsVersion8
const (
BlobStore_PutBlob_FullMethodName = "/resource.BlobStore/PutBlob"
BlobStore_GetBlob_FullMethodName = "/resource.BlobStore/GetBlob"
)
// BlobStoreClient is the client API for BlobStore service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type BlobStoreClient interface {
// Upload a blob that will be saved in a resource
PutBlob(ctx context.Context, in *PutBlobRequest, opts ...grpc.CallOption) (*PutBlobResponse, error)
// Get blob contents. When possible, this will return a signed URL
// For large payloads, signed URLs are required to avoid protobuf message size limits
GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (*GetBlobResponse, error)
}
type blobStoreClient struct {
cc grpc.ClientConnInterface
}
func NewBlobStoreClient(cc grpc.ClientConnInterface) BlobStoreClient {
return &blobStoreClient{cc}
}
func (c *blobStoreClient) PutBlob(ctx context.Context, in *PutBlobRequest, opts ...grpc.CallOption) (*PutBlobResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(PutBlobResponse)
err := c.cc.Invoke(ctx, BlobStore_PutBlob_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *blobStoreClient) GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (*GetBlobResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetBlobResponse)
err := c.cc.Invoke(ctx, BlobStore_GetBlob_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// BlobStoreServer is the server API for BlobStore service.
// All implementations should embed UnimplementedBlobStoreServer
// for forward compatibility
type BlobStoreServer interface {
// Upload a blob that will be saved in a resource
PutBlob(context.Context, *PutBlobRequest) (*PutBlobResponse, error)
// Get blob contents. When possible, this will return a signed URL
// For large payloads, signed URLs are required to avoid protobuf message size limits
GetBlob(context.Context, *GetBlobRequest) (*GetBlobResponse, error)
}
// UnimplementedBlobStoreServer should be embedded to have forward compatible implementations.
type UnimplementedBlobStoreServer struct {
}
func (UnimplementedBlobStoreServer) PutBlob(context.Context, *PutBlobRequest) (*PutBlobResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PutBlob not implemented")
}
func (UnimplementedBlobStoreServer) GetBlob(context.Context, *GetBlobRequest) (*GetBlobResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlob not implemented")
}
// UnsafeBlobStoreServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to BlobStoreServer will
// result in compilation errors.
type UnsafeBlobStoreServer interface {
mustEmbedUnimplementedBlobStoreServer()
}
func RegisterBlobStoreServer(s grpc.ServiceRegistrar, srv BlobStoreServer) {
s.RegisterService(&BlobStore_ServiceDesc, srv)
}
func _BlobStore_PutBlob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PutBlobRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BlobStoreServer).PutBlob(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: BlobStore_PutBlob_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BlobStoreServer).PutBlob(ctx, req.(*PutBlobRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BlobStore_GetBlob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlobRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BlobStoreServer).GetBlob(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: BlobStore_GetBlob_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BlobStoreServer).GetBlob(ctx, req.(*GetBlobRequest))
}
return interceptor(ctx, in, info, handler)
}
// BlobStore_ServiceDesc is the grpc.ServiceDesc for BlobStore service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var BlobStore_ServiceDesc = grpc.ServiceDesc{
ServiceName: "resource.BlobStore",
HandlerType: (*BlobStoreServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "PutBlob",
Handler: _BlobStore_PutBlob_Handler,
},
{
MethodName: "GetBlob",
Handler: _BlobStore_GetBlob_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "blob.proto",
}
+18
View File
@@ -0,0 +1,18 @@
module github.com/grafana/grafana/pkg/storage/unified/resourcepb
go 1.24.3
require (
google.golang.org/grpc v1.72.1
google.golang.org/protobuf v1.36.6
)
require (
github.com/google/go-cmp v0.7.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
)
+34
View File
@@ -0,0 +1,34 @@
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
@@ -4,7 +4,7 @@
// - protoc (unknown)
// source: resource.proto
package resource
package resourcepb
import (
context "context"
@@ -476,141 +476,6 @@ var BulkStore_ServiceDesc = grpc.ServiceDesc{
Metadata: "resource.proto",
}
const (
ResourceIndex_Search_FullMethodName = "/resource.ResourceIndex/Search"
ResourceIndex_GetStats_FullMethodName = "/resource.ResourceIndex/GetStats"
)
// ResourceIndexClient is the client API for ResourceIndex service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
type ResourceIndexClient interface {
Search(ctx context.Context, in *ResourceSearchRequest, opts ...grpc.CallOption) (*ResourceSearchResponse, error)
// Get the resource stats
GetStats(ctx context.Context, in *ResourceStatsRequest, opts ...grpc.CallOption) (*ResourceStatsResponse, error)
}
type resourceIndexClient struct {
cc grpc.ClientConnInterface
}
func NewResourceIndexClient(cc grpc.ClientConnInterface) ResourceIndexClient {
return &resourceIndexClient{cc}
}
func (c *resourceIndexClient) Search(ctx context.Context, in *ResourceSearchRequest, opts ...grpc.CallOption) (*ResourceSearchResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResourceSearchResponse)
err := c.cc.Invoke(ctx, ResourceIndex_Search_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *resourceIndexClient) GetStats(ctx context.Context, in *ResourceStatsRequest, opts ...grpc.CallOption) (*ResourceStatsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResourceStatsResponse)
err := c.cc.Invoke(ctx, ResourceIndex_GetStats_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// ResourceIndexServer is the server API for ResourceIndex service.
// All implementations should embed UnimplementedResourceIndexServer
// for forward compatibility
//
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
type ResourceIndexServer interface {
Search(context.Context, *ResourceSearchRequest) (*ResourceSearchResponse, error)
// Get the resource stats
GetStats(context.Context, *ResourceStatsRequest) (*ResourceStatsResponse, error)
}
// UnimplementedResourceIndexServer should be embedded to have forward compatible implementations.
type UnimplementedResourceIndexServer struct {
}
func (UnimplementedResourceIndexServer) Search(context.Context, *ResourceSearchRequest) (*ResourceSearchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Search not implemented")
}
func (UnimplementedResourceIndexServer) GetStats(context.Context, *ResourceStatsRequest) (*ResourceStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetStats not implemented")
}
// UnsafeResourceIndexServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ResourceIndexServer will
// result in compilation errors.
type UnsafeResourceIndexServer interface {
mustEmbedUnimplementedResourceIndexServer()
}
func RegisterResourceIndexServer(s grpc.ServiceRegistrar, srv ResourceIndexServer) {
s.RegisterService(&ResourceIndex_ServiceDesc, srv)
}
func _ResourceIndex_Search_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResourceSearchRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResourceIndexServer).Search(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ResourceIndex_Search_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResourceIndexServer).Search(ctx, req.(*ResourceSearchRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ResourceIndex_GetStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResourceStatsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResourceIndexServer).GetStats(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ResourceIndex_GetStats_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResourceIndexServer).GetStats(ctx, req.(*ResourceStatsRequest))
}
return interceptor(ctx, in, info, handler)
}
// ResourceIndex_ServiceDesc is the grpc.ServiceDesc for ResourceIndex service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ResourceIndex_ServiceDesc = grpc.ServiceDesc{
ServiceName: "resource.ResourceIndex",
HandlerType: (*ResourceIndexServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Search",
Handler: _ResourceIndex_Search_Handler,
},
{
MethodName: "GetStats",
Handler: _ResourceIndex_GetStats_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "resource.proto",
}
const (
ManagedObjectIndex_CountManagedObjects_FullMethodName = "/resource.ManagedObjectIndex/CountManagedObjects"
ManagedObjectIndex_ListManagedObjects_FullMethodName = "/resource.ManagedObjectIndex/ListManagedObjects"
@@ -748,139 +613,6 @@ var ManagedObjectIndex_ServiceDesc = grpc.ServiceDesc{
Metadata: "resource.proto",
}
const (
BlobStore_PutBlob_FullMethodName = "/resource.BlobStore/PutBlob"
BlobStore_GetBlob_FullMethodName = "/resource.BlobStore/GetBlob"
)
// BlobStoreClient is the client API for BlobStore service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type BlobStoreClient interface {
// Upload a blob that will be saved in a resource
PutBlob(ctx context.Context, in *PutBlobRequest, opts ...grpc.CallOption) (*PutBlobResponse, error)
// Get blob contents. When possible, this will return a signed URL
// For large payloads, signed URLs are required to avoid protobuf message size limits
GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (*GetBlobResponse, error)
}
type blobStoreClient struct {
cc grpc.ClientConnInterface
}
func NewBlobStoreClient(cc grpc.ClientConnInterface) BlobStoreClient {
return &blobStoreClient{cc}
}
func (c *blobStoreClient) PutBlob(ctx context.Context, in *PutBlobRequest, opts ...grpc.CallOption) (*PutBlobResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(PutBlobResponse)
err := c.cc.Invoke(ctx, BlobStore_PutBlob_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *blobStoreClient) GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (*GetBlobResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetBlobResponse)
err := c.cc.Invoke(ctx, BlobStore_GetBlob_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// BlobStoreServer is the server API for BlobStore service.
// All implementations should embed UnimplementedBlobStoreServer
// for forward compatibility
type BlobStoreServer interface {
// Upload a blob that will be saved in a resource
PutBlob(context.Context, *PutBlobRequest) (*PutBlobResponse, error)
// Get blob contents. When possible, this will return a signed URL
// For large payloads, signed URLs are required to avoid protobuf message size limits
GetBlob(context.Context, *GetBlobRequest) (*GetBlobResponse, error)
}
// UnimplementedBlobStoreServer should be embedded to have forward compatible implementations.
type UnimplementedBlobStoreServer struct {
}
func (UnimplementedBlobStoreServer) PutBlob(context.Context, *PutBlobRequest) (*PutBlobResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PutBlob not implemented")
}
func (UnimplementedBlobStoreServer) GetBlob(context.Context, *GetBlobRequest) (*GetBlobResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlob not implemented")
}
// UnsafeBlobStoreServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to BlobStoreServer will
// result in compilation errors.
type UnsafeBlobStoreServer interface {
mustEmbedUnimplementedBlobStoreServer()
}
func RegisterBlobStoreServer(s grpc.ServiceRegistrar, srv BlobStoreServer) {
s.RegisterService(&BlobStore_ServiceDesc, srv)
}
func _BlobStore_PutBlob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PutBlobRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BlobStoreServer).PutBlob(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: BlobStore_PutBlob_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BlobStoreServer).PutBlob(ctx, req.(*PutBlobRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BlobStore_GetBlob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlobRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BlobStoreServer).GetBlob(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: BlobStore_GetBlob_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BlobStoreServer).GetBlob(ctx, req.(*GetBlobRequest))
}
return interceptor(ctx, in, info, handler)
}
// BlobStore_ServiceDesc is the grpc.ServiceDesc for BlobStore service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var BlobStore_ServiceDesc = grpc.ServiceDesc{
ServiceName: "resource.BlobStore",
HandlerType: (*BlobStoreServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "PutBlob",
Handler: _BlobStore_PutBlob_Handler,
},
{
MethodName: "GetBlob",
Handler: _BlobStore_GetBlob_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "resource.proto",
}
const (
Diagnostics_IsHealthy_FullMethodName = "/resource.Diagnostics/IsHealthy"
)
+879
View File
@@ -0,0 +1,879 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.5
// protoc (unknown)
// source: search.proto
package resourcepb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Get statistics across multiple resources
// For these queries, we do not need authorization to see the actual values
type ResourceStatsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Namespace (tenant)
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
// An optional list of group/resource identifiers
// when empty, we assume searching across everything
// NOTE, this query may need to federate across a few storage instances
Kinds []string `protobuf:"bytes,2,rep,name=kinds,proto3" json:"kinds,omitempty"`
// Limit the stats within a folder (not recursive!)
Folder string `protobuf:"bytes,3,opt,name=folder,proto3" json:"folder,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceStatsRequest) Reset() {
*x = ResourceStatsRequest{}
mi := &file_search_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceStatsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceStatsRequest) ProtoMessage() {}
func (x *ResourceStatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceStatsRequest.ProtoReflect.Descriptor instead.
func (*ResourceStatsRequest) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{0}
}
func (x *ResourceStatsRequest) GetNamespace() string {
if x != nil {
return x.Namespace
}
return ""
}
func (x *ResourceStatsRequest) GetKinds() []string {
if x != nil {
return x.Kinds
}
return nil
}
func (x *ResourceStatsRequest) GetFolder() string {
if x != nil {
return x.Folder
}
return ""
}
type ResourceStatsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Error details
Error *ErrorResult `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
// All results exist within this key
Stats []*ResourceStatsResponse_Stats `protobuf:"bytes,2,rep,name=stats,proto3" json:"stats,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceStatsResponse) Reset() {
*x = ResourceStatsResponse{}
mi := &file_search_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceStatsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceStatsResponse) ProtoMessage() {}
func (x *ResourceStatsResponse) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceStatsResponse.ProtoReflect.Descriptor instead.
func (*ResourceStatsResponse) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{1}
}
func (x *ResourceStatsResponse) GetError() *ErrorResult {
if x != nil {
return x.Error
}
return nil
}
func (x *ResourceStatsResponse) GetStats() []*ResourceStatsResponse_Stats {
if x != nil {
return x.Stats
}
return nil
}
// Search within a single resource
type ResourceSearchRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The key must include namespace + group + resource
Options *ListOptions `protobuf:"bytes,1,opt,name=options,proto3" json:"options,omitempty"`
// To search additional resource types, add additional keys to this list
// NOTE: queries will only support federation across kinds with common fields
Federated []*ResourceKey `protobuf:"bytes,2,rep,name=federated,proto3" json:"federated,omitempty"`
// When a query exists, it is parsed and used to influence
// query string for chosen implementation (currently just bleve)
// The score is only relevant when a query exists
Query string `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"`
// max results
Limit int64 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"`
// where to start the query (eg, From)
Offset int64 `protobuf:"varint,5,opt,name=offset,proto3" json:"offset,omitempty"`
// sorting
SortBy []*ResourceSearchRequest_Sort `protobuf:"bytes,6,rep,name=sortBy,proto3" json:"sortBy,omitempty"`
// calculate field statistics
Facet map[string]*ResourceSearchRequest_Facet `protobuf:"bytes,7,rep,name=facet,proto3" json:"facet,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
// the return fields (empty will return everything)
Fields []string `protobuf:"bytes,8,rep,name=fields,proto3" json:"fields,omitempty"`
// explain each result (added to the each row)
Explain bool `protobuf:"varint,9,opt,name=explain,proto3" json:"explain,omitempty"`
IsDeleted bool `protobuf:"varint,10,opt,name=is_deleted,json=isDeleted,proto3" json:"is_deleted,omitempty"`
Page int64 `protobuf:"varint,11,opt,name=page,proto3" json:"page,omitempty"`
Permission int64 `protobuf:"varint,12,opt,name=permission,proto3" json:"permission,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceSearchRequest) Reset() {
*x = ResourceSearchRequest{}
mi := &file_search_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceSearchRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceSearchRequest) ProtoMessage() {}
func (x *ResourceSearchRequest) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceSearchRequest.ProtoReflect.Descriptor instead.
func (*ResourceSearchRequest) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{2}
}
func (x *ResourceSearchRequest) GetOptions() *ListOptions {
if x != nil {
return x.Options
}
return nil
}
func (x *ResourceSearchRequest) GetFederated() []*ResourceKey {
if x != nil {
return x.Federated
}
return nil
}
func (x *ResourceSearchRequest) GetQuery() string {
if x != nil {
return x.Query
}
return ""
}
func (x *ResourceSearchRequest) GetLimit() int64 {
if x != nil {
return x.Limit
}
return 0
}
func (x *ResourceSearchRequest) GetOffset() int64 {
if x != nil {
return x.Offset
}
return 0
}
func (x *ResourceSearchRequest) GetSortBy() []*ResourceSearchRequest_Sort {
if x != nil {
return x.SortBy
}
return nil
}
func (x *ResourceSearchRequest) GetFacet() map[string]*ResourceSearchRequest_Facet {
if x != nil {
return x.Facet
}
return nil
}
func (x *ResourceSearchRequest) GetFields() []string {
if x != nil {
return x.Fields
}
return nil
}
func (x *ResourceSearchRequest) GetExplain() bool {
if x != nil {
return x.Explain
}
return false
}
func (x *ResourceSearchRequest) GetIsDeleted() bool {
if x != nil {
return x.IsDeleted
}
return false
}
func (x *ResourceSearchRequest) GetPage() int64 {
if x != nil {
return x.Page
}
return 0
}
func (x *ResourceSearchRequest) GetPermission() int64 {
if x != nil {
return x.Permission
}
return 0
}
type ResourceSearchResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Error details
Error *ErrorResult `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
// All results exist within this key
Key *ResourceKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
// Query results
Results *ResourceTable `protobuf:"bytes,3,opt,name=results,proto3" json:"results,omitempty"`
// The total hit count
TotalHits int64 `protobuf:"varint,4,opt,name=total_hits,json=totalHits,proto3" json:"total_hits,omitempty"`
// indicates how expensive was the query with respect to bytes read
QueryCost float64 `protobuf:"fixed64,5,opt,name=query_cost,json=queryCost,proto3" json:"query_cost,omitempty"`
// maximum score across all fields
MaxScore float64 `protobuf:"fixed64,6,opt,name=max_score,json=maxScore,proto3" json:"max_score,omitempty"`
// Facet results
Facet map[string]*ResourceSearchResponse_Facet `protobuf:"bytes,7,rep,name=facet,proto3" json:"facet,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceSearchResponse) Reset() {
*x = ResourceSearchResponse{}
mi := &file_search_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceSearchResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceSearchResponse) ProtoMessage() {}
func (x *ResourceSearchResponse) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceSearchResponse.ProtoReflect.Descriptor instead.
func (*ResourceSearchResponse) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{3}
}
func (x *ResourceSearchResponse) GetError() *ErrorResult {
if x != nil {
return x.Error
}
return nil
}
func (x *ResourceSearchResponse) GetKey() *ResourceKey {
if x != nil {
return x.Key
}
return nil
}
func (x *ResourceSearchResponse) GetResults() *ResourceTable {
if x != nil {
return x.Results
}
return nil
}
func (x *ResourceSearchResponse) GetTotalHits() int64 {
if x != nil {
return x.TotalHits
}
return 0
}
func (x *ResourceSearchResponse) GetQueryCost() float64 {
if x != nil {
return x.QueryCost
}
return 0
}
func (x *ResourceSearchResponse) GetMaxScore() float64 {
if x != nil {
return x.MaxScore
}
return 0
}
func (x *ResourceSearchResponse) GetFacet() map[string]*ResourceSearchResponse_Facet {
if x != nil {
return x.Facet
}
return nil
}
type ResourceStatsResponse_Stats struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Resource group
Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"`
// Resource name
Resource string `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"`
// Number of items
Count int64 `protobuf:"varint,3,opt,name=count,proto3" json:"count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceStatsResponse_Stats) Reset() {
*x = ResourceStatsResponse_Stats{}
mi := &file_search_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceStatsResponse_Stats) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceStatsResponse_Stats) ProtoMessage() {}
func (x *ResourceStatsResponse_Stats) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceStatsResponse_Stats.ProtoReflect.Descriptor instead.
func (*ResourceStatsResponse_Stats) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{1, 0}
}
func (x *ResourceStatsResponse_Stats) GetGroup() string {
if x != nil {
return x.Group
}
return ""
}
func (x *ResourceStatsResponse_Stats) GetResource() string {
if x != nil {
return x.Resource
}
return ""
}
func (x *ResourceStatsResponse_Stats) GetCount() int64 {
if x != nil {
return x.Count
}
return 0
}
type ResourceSearchRequest_Sort struct {
state protoimpl.MessageState `protogen:"open.v1"`
Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"`
Desc bool `protobuf:"varint,2,opt,name=desc,proto3" json:"desc,omitempty"` // defaults to ascending
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceSearchRequest_Sort) Reset() {
*x = ResourceSearchRequest_Sort{}
mi := &file_search_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceSearchRequest_Sort) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceSearchRequest_Sort) ProtoMessage() {}
func (x *ResourceSearchRequest_Sort) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceSearchRequest_Sort.ProtoReflect.Descriptor instead.
func (*ResourceSearchRequest_Sort) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{2, 0}
}
func (x *ResourceSearchRequest_Sort) GetField() string {
if x != nil {
return x.Field
}
return ""
}
func (x *ResourceSearchRequest_Sort) GetDesc() bool {
if x != nil {
return x.Desc
}
return false
}
type ResourceSearchRequest_Facet struct {
state protoimpl.MessageState `protogen:"open.v1"`
Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"`
Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceSearchRequest_Facet) Reset() {
*x = ResourceSearchRequest_Facet{}
mi := &file_search_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceSearchRequest_Facet) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceSearchRequest_Facet) ProtoMessage() {}
func (x *ResourceSearchRequest_Facet) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceSearchRequest_Facet.ProtoReflect.Descriptor instead.
func (*ResourceSearchRequest_Facet) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{2, 1}
}
func (x *ResourceSearchRequest_Facet) GetField() string {
if x != nil {
return x.Field
}
return ""
}
func (x *ResourceSearchRequest_Facet) GetLimit() int64 {
if x != nil {
return x.Limit
}
return 0
}
type ResourceSearchResponse_Facet struct {
state protoimpl.MessageState `protogen:"open.v1"`
Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"`
// The distinct terms
Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"`
// The number of documents that do *not* have this field
Missing int64 `protobuf:"varint,3,opt,name=missing,proto3" json:"missing,omitempty"`
// Top term stats
Terms []*ResourceSearchResponse_TermFacet `protobuf:"bytes,4,rep,name=terms,proto3" json:"terms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceSearchResponse_Facet) Reset() {
*x = ResourceSearchResponse_Facet{}
mi := &file_search_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceSearchResponse_Facet) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceSearchResponse_Facet) ProtoMessage() {}
func (x *ResourceSearchResponse_Facet) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceSearchResponse_Facet.ProtoReflect.Descriptor instead.
func (*ResourceSearchResponse_Facet) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{3, 0}
}
func (x *ResourceSearchResponse_Facet) GetField() string {
if x != nil {
return x.Field
}
return ""
}
func (x *ResourceSearchResponse_Facet) GetTotal() int64 {
if x != nil {
return x.Total
}
return 0
}
func (x *ResourceSearchResponse_Facet) GetMissing() int64 {
if x != nil {
return x.Missing
}
return 0
}
func (x *ResourceSearchResponse_Facet) GetTerms() []*ResourceSearchResponse_TermFacet {
if x != nil {
return x.Terms
}
return nil
}
type ResourceSearchResponse_TermFacet struct {
state protoimpl.MessageState `protogen:"open.v1"`
Term string `protobuf:"bytes,1,opt,name=term,proto3" json:"term,omitempty"`
Count int64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceSearchResponse_TermFacet) Reset() {
*x = ResourceSearchResponse_TermFacet{}
mi := &file_search_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceSearchResponse_TermFacet) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceSearchResponse_TermFacet) ProtoMessage() {}
func (x *ResourceSearchResponse_TermFacet) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceSearchResponse_TermFacet.ProtoReflect.Descriptor instead.
func (*ResourceSearchResponse_TermFacet) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{3, 1}
}
func (x *ResourceSearchResponse_TermFacet) GetTerm() string {
if x != nil {
return x.Term
}
return ""
}
func (x *ResourceSearchResponse_TermFacet) GetCount() int64 {
if x != nil {
return x.Count
}
return 0
}
var File_search_proto protoreflect.FileDescriptor
var file_search_proto_rawDesc = string([]byte{
0x0a, 0x0c, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x1a, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x62, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14,
0x0a, 0x05, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6b,
0x69, 0x6e, 0x64, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x22, 0xd2, 0x01, 0x0a,
0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73,
0x1a, 0x4f, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f,
0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12,
0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x22, 0x8e, 0x05, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65,
0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x6f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x33, 0x0a, 0x09,
0x66, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x66, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65,
0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74,
0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a,
0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f,
0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x18,
0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x06, 0x73, 0x6f, 0x72,
0x74, 0x42, 0x79, 0x12, 0x40, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x18, 0x07, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05,
0x66, 0x61, 0x63, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18,
0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x18, 0x0a,
0x07, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
0x65, 0x78, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x64, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65,
0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a,
0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x30, 0x0a, 0x04, 0x53, 0x6f,
0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63,
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x1a, 0x33, 0x0a, 0x05,
0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c,
0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69,
0x74, 0x1a, 0x5f, 0x0a, 0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
0x79, 0x12, 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0xea, 0x04, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53,
0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a,
0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03,
0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e,
0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x07, 0x72,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
0x68, 0x69, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61,
0x6c, 0x48, 0x69, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x63,
0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79,
0x43, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x63, 0x6f, 0x72,
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x53, 0x63, 0x6f, 0x72,
0x65, 0x12, 0x41, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x66,
0x61, 0x63, 0x65, 0x74, 0x1a, 0x8f, 0x01, 0x0a, 0x05, 0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x14,
0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66,
0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20,
0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69,
0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x73,
0x73, 0x69, 0x6e, 0x67, 0x12, 0x40, 0x0a, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x04, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52,
0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x1a, 0x35, 0x0a, 0x09, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61,
0x63, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x60, 0x0a,
0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3c, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46,
0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32,
0xa9, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65,
0x78, 0x12, 0x4b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53,
0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b,
0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74,
0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74,
0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3b, 0x5a, 0x39, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e,
0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
file_search_proto_rawDescOnce sync.Once
file_search_proto_rawDescData []byte
)
func file_search_proto_rawDescGZIP() []byte {
file_search_proto_rawDescOnce.Do(func() {
file_search_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_search_proto_rawDesc), len(file_search_proto_rawDesc)))
})
return file_search_proto_rawDescData
}
var file_search_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_search_proto_goTypes = []any{
(*ResourceStatsRequest)(nil), // 0: resource.ResourceStatsRequest
(*ResourceStatsResponse)(nil), // 1: resource.ResourceStatsResponse
(*ResourceSearchRequest)(nil), // 2: resource.ResourceSearchRequest
(*ResourceSearchResponse)(nil), // 3: resource.ResourceSearchResponse
(*ResourceStatsResponse_Stats)(nil), // 4: resource.ResourceStatsResponse.Stats
(*ResourceSearchRequest_Sort)(nil), // 5: resource.ResourceSearchRequest.Sort
(*ResourceSearchRequest_Facet)(nil), // 6: resource.ResourceSearchRequest.Facet
nil, // 7: resource.ResourceSearchRequest.FacetEntry
(*ResourceSearchResponse_Facet)(nil), // 8: resource.ResourceSearchResponse.Facet
(*ResourceSearchResponse_TermFacet)(nil), // 9: resource.ResourceSearchResponse.TermFacet
nil, // 10: resource.ResourceSearchResponse.FacetEntry
(*ErrorResult)(nil), // 11: resource.ErrorResult
(*ListOptions)(nil), // 12: resource.ListOptions
(*ResourceKey)(nil), // 13: resource.ResourceKey
(*ResourceTable)(nil), // 14: resource.ResourceTable
}
var file_search_proto_depIdxs = []int32{
11, // 0: resource.ResourceStatsResponse.error:type_name -> resource.ErrorResult
4, // 1: resource.ResourceStatsResponse.stats:type_name -> resource.ResourceStatsResponse.Stats
12, // 2: resource.ResourceSearchRequest.options:type_name -> resource.ListOptions
13, // 3: resource.ResourceSearchRequest.federated:type_name -> resource.ResourceKey
5, // 4: resource.ResourceSearchRequest.sortBy:type_name -> resource.ResourceSearchRequest.Sort
7, // 5: resource.ResourceSearchRequest.facet:type_name -> resource.ResourceSearchRequest.FacetEntry
11, // 6: resource.ResourceSearchResponse.error:type_name -> resource.ErrorResult
13, // 7: resource.ResourceSearchResponse.key:type_name -> resource.ResourceKey
14, // 8: resource.ResourceSearchResponse.results:type_name -> resource.ResourceTable
10, // 9: resource.ResourceSearchResponse.facet:type_name -> resource.ResourceSearchResponse.FacetEntry
6, // 10: resource.ResourceSearchRequest.FacetEntry.value:type_name -> resource.ResourceSearchRequest.Facet
9, // 11: resource.ResourceSearchResponse.Facet.terms:type_name -> resource.ResourceSearchResponse.TermFacet
8, // 12: resource.ResourceSearchResponse.FacetEntry.value:type_name -> resource.ResourceSearchResponse.Facet
2, // 13: resource.ResourceIndex.Search:input_type -> resource.ResourceSearchRequest
0, // 14: resource.ResourceIndex.GetStats:input_type -> resource.ResourceStatsRequest
3, // 15: resource.ResourceIndex.Search:output_type -> resource.ResourceSearchResponse
1, // 16: resource.ResourceIndex.GetStats:output_type -> resource.ResourceStatsResponse
15, // [15:17] is the sub-list for method output_type
13, // [13:15] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
}
func init() { file_search_proto_init() }
func file_search_proto_init() {
if File_search_proto != nil {
return
}
file_resource_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_search_proto_rawDesc), len(file_search_proto_rawDesc)),
NumEnums: 0,
NumMessages: 11,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_search_proto_goTypes,
DependencyIndexes: file_search_proto_depIdxs,
MessageInfos: file_search_proto_msgTypes,
}.Build()
File_search_proto = out.File
file_search_proto_goTypes = nil
file_search_proto_depIdxs = nil
}
@@ -0,0 +1,154 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.4.0
// - protoc (unknown)
// source: search.proto
package resourcepb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.62.0 or later.
const _ = grpc.SupportPackageIsVersion8
const (
ResourceIndex_Search_FullMethodName = "/resource.ResourceIndex/Search"
ResourceIndex_GetStats_FullMethodName = "/resource.ResourceIndex/GetStats"
)
// ResourceIndexClient is the client API for ResourceIndex service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
type ResourceIndexClient interface {
Search(ctx context.Context, in *ResourceSearchRequest, opts ...grpc.CallOption) (*ResourceSearchResponse, error)
// Get the resource stats
GetStats(ctx context.Context, in *ResourceStatsRequest, opts ...grpc.CallOption) (*ResourceStatsResponse, error)
}
type resourceIndexClient struct {
cc grpc.ClientConnInterface
}
func NewResourceIndexClient(cc grpc.ClientConnInterface) ResourceIndexClient {
return &resourceIndexClient{cc}
}
func (c *resourceIndexClient) Search(ctx context.Context, in *ResourceSearchRequest, opts ...grpc.CallOption) (*ResourceSearchResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResourceSearchResponse)
err := c.cc.Invoke(ctx, ResourceIndex_Search_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *resourceIndexClient) GetStats(ctx context.Context, in *ResourceStatsRequest, opts ...grpc.CallOption) (*ResourceStatsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResourceStatsResponse)
err := c.cc.Invoke(ctx, ResourceIndex_GetStats_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// ResourceIndexServer is the server API for ResourceIndex service.
// All implementations should embed UnimplementedResourceIndexServer
// for forward compatibility
//
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
type ResourceIndexServer interface {
Search(context.Context, *ResourceSearchRequest) (*ResourceSearchResponse, error)
// Get the resource stats
GetStats(context.Context, *ResourceStatsRequest) (*ResourceStatsResponse, error)
}
// UnimplementedResourceIndexServer should be embedded to have forward compatible implementations.
type UnimplementedResourceIndexServer struct {
}
func (UnimplementedResourceIndexServer) Search(context.Context, *ResourceSearchRequest) (*ResourceSearchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Search not implemented")
}
func (UnimplementedResourceIndexServer) GetStats(context.Context, *ResourceStatsRequest) (*ResourceStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetStats not implemented")
}
// UnsafeResourceIndexServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ResourceIndexServer will
// result in compilation errors.
type UnsafeResourceIndexServer interface {
mustEmbedUnimplementedResourceIndexServer()
}
func RegisterResourceIndexServer(s grpc.ServiceRegistrar, srv ResourceIndexServer) {
s.RegisterService(&ResourceIndex_ServiceDesc, srv)
}
func _ResourceIndex_Search_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResourceSearchRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResourceIndexServer).Search(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ResourceIndex_Search_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResourceIndexServer).Search(ctx, req.(*ResourceSearchRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ResourceIndex_GetStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResourceStatsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResourceIndexServer).GetStats(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ResourceIndex_GetStats_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResourceIndexServer).GetStats(ctx, req.(*ResourceStatsRequest))
}
return interceptor(ctx, in, info, handler)
}
// ResourceIndex_ServiceDesc is the grpc.ServiceDesc for ResourceIndex service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ResourceIndex_ServiceDesc = grpc.ServiceDesc{
ServiceName: "resource.ResourceIndex",
HandlerType: (*ResourceIndexServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Search",
Handler: _ResourceIndex_Search_Handler,
},
{
MethodName: "GetStats",
Handler: _ResourceIndex_GetStats_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "search.proto",
}
+39 -37
View File
@@ -27,8 +27,10 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/storage/unified/resource"
@@ -309,7 +311,7 @@ type bleveIndex struct {
fields resource.SearchableDocumentFields
// The values returned with all
allFields []*resource.ResourceTableColumnDefinition
allFields []*resourcepb.ResourceTableColumnDefinition
features featuremgmt.FeatureToggles
tracing trace.Tracer
}
@@ -330,29 +332,29 @@ func (b *bleveIndex) BulkIndex(req *resource.BulkIndexRequest) error {
doc := item.Doc.UpdateCopyFields()
doc.References = nil // remove references (for now!)
err := batch.Index(doc.Key.SearchID(), doc)
err := batch.Index(resource.SearchID(doc.Key), doc)
if err != nil {
return err
}
case resource.ActionDelete:
batch.Delete(item.Key.SearchID())
batch.Delete(resource.SearchID(item.Key))
}
}
return b.index.Batch(batch)
}
func (b *bleveIndex) ListManagedObjects(ctx context.Context, req *resource.ListManagedObjectsRequest) (*resource.ListManagedObjectsResponse, error) {
func (b *bleveIndex) ListManagedObjects(ctx context.Context, req *resourcepb.ListManagedObjectsRequest) (*resourcepb.ListManagedObjectsResponse, error) {
if req.NextPageToken != "" {
return nil, fmt.Errorf("next page not implemented yet")
}
if req.Kind == "" {
return &resource.ListManagedObjectsResponse{
return &resourcepb.ListManagedObjectsResponse{
Error: resource.NewBadRequestError("empty manager kind"),
}, nil
}
if req.Id == "" {
return &resource.ListManagedObjectsResponse{
return &resourcepb.ListManagedObjectsResponse{
Error: resource.NewBadRequestError("empty manager id"),
}, nil
}
@@ -423,17 +425,17 @@ func (b *bleveIndex) ListManagedObjects(ctx context.Context, req *resource.ListM
return 0
}
rsp := &resource.ListManagedObjectsResponse{}
rsp := &resourcepb.ListManagedObjectsResponse{}
for _, hit := range found.Hits {
item := &resource.ListManagedObjectsResponse_Item{
Object: &resource.ResourceKey{},
item := &resourcepb.ListManagedObjectsResponse_Item{
Object: &resourcepb.ResourceKey{},
Hash: asString(hit.Fields[resource.SEARCH_FIELD_SOURCE_CHECKSUM]),
Path: asString(hit.Fields[resource.SEARCH_FIELD_SOURCE_PATH]),
Time: asTime(hit.Fields[resource.SEARCH_FIELD_SOURCE_TIME]),
Title: asString(hit.Fields[resource.SEARCH_FIELD_TITLE]),
Folder: asString(hit.Fields[resource.SEARCH_FIELD_FOLDER]),
}
err := item.Object.ReadSearchID(hit.ID)
err := resource.ReadSearchID(item.Object, hit.ID)
if err != nil {
return nil, err
}
@@ -442,7 +444,7 @@ func (b *bleveIndex) ListManagedObjects(ctx context.Context, req *resource.ListM
return rsp, nil
}
func (b *bleveIndex) CountManagedObjects(ctx context.Context) ([]*resource.CountManagedObjectsResponse_ResourceCount, error) {
func (b *bleveIndex) CountManagedObjects(ctx context.Context) ([]*resourcepb.CountManagedObjectsResponse_ResourceCount, error) {
found, err := b.index.SearchInContext(ctx, &bleve.SearchRequest{
Query: bleve.NewMatchAllQuery(),
Size: 0,
@@ -453,14 +455,14 @@ func (b *bleveIndex) CountManagedObjects(ctx context.Context) ([]*resource.Count
if err != nil {
return nil, err
}
vals := make([]*resource.CountManagedObjectsResponse_ResourceCount, 0)
vals := make([]*resourcepb.CountManagedObjectsResponse_ResourceCount, 0)
f, ok := found.Facets["count"]
if ok && f.Terms != nil {
for _, v := range f.Terms.Terms() {
val := v.Term
idx := strings.Index(val, ":")
if idx > 0 {
vals = append(vals, &resource.CountManagedObjectsResponse_ResourceCount{
vals = append(vals, &resourcepb.CountManagedObjectsResponse_ResourceCount{
Kind: val[0:idx],
Id: val[idx+1:],
Group: b.key.Group,
@@ -477,19 +479,19 @@ func (b *bleveIndex) CountManagedObjects(ctx context.Context) ([]*resource.Count
func (b *bleveIndex) Search(
ctx context.Context,
access authlib.AccessClient,
req *resource.ResourceSearchRequest,
req *resourcepb.ResourceSearchRequest,
federate []resource.ResourceIndex, // For federated queries, these will match the values in req.federate
) (*resource.ResourceSearchResponse, error) {
) (*resourcepb.ResourceSearchResponse, error) {
ctx, span := b.tracing.Start(ctx, tracingPrexfixBleve+"Search")
defer span.End()
if req.Options == nil || req.Options.Key == nil {
return &resource.ResourceSearchResponse{
return &resourcepb.ResourceSearchResponse{
Error: resource.NewBadRequestError("missing query key"),
}, nil
}
response := &resource.ResourceSearchResponse{
response := &resourcepb.ResourceSearchResponse{
Error: b.verifyKey(req.Options.Key),
}
if response.Error != nil {
@@ -545,7 +547,7 @@ func (b *bleveIndex) Search(
for k, v := range res.Facets {
f := newResponseFacet(v)
if response.Facet == nil {
response.Facet = make(map[string]*resource.ResourceSearchResponse_Facet)
response.Facet = make(map[string]*resourcepb.ResourceSearchResponse_Facet)
}
response.Facet[k] = f
}
@@ -577,7 +579,7 @@ func (b *bleveIndex) DocCount(ctx context.Context, folder string) (int64, error)
}
// make sure the request key matches the index
func (b *bleveIndex) verifyKey(key *resource.ResourceKey) *resource.ErrorResult {
func (b *bleveIndex) verifyKey(key *resourcepb.ResourceKey) *resourcepb.ErrorResult {
if key.Namespace != b.key.Namespace {
return resource.NewBadRequestError("namespace mismatch (expected " + b.key.Namespace + ")")
}
@@ -592,7 +594,7 @@ func (b *bleveIndex) verifyKey(key *resource.ResourceKey) *resource.ErrorResult
func (b *bleveIndex) getIndex(
ctx context.Context,
req *resource.ResourceSearchRequest,
req *resourcepb.ResourceSearchRequest,
federate []resource.ResourceIndex,
) (bleve.Index, error) {
_, span := b.tracing.Start(ctx, tracingPrexfixBleve+"getIndex")
@@ -621,7 +623,7 @@ func (b *bleveIndex) getIndex(
return b.index, nil
}
func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resource.ResourceSearchRequest, access authlib.AccessClient) (*bleve.SearchRequest, *resource.ErrorResult) {
func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resourcepb.ResourceSearchRequest, access authlib.AccessClient) (*bleve.SearchRequest, *resourcepb.ErrorResult) {
ctx, span := b.tracing.Start(ctx, tracingPrexfixBleve+"toBleveSearchRequest")
defer span.End()
@@ -793,7 +795,7 @@ func safeInt64ToInt(i64 int64) (int, error) {
return int(i64), nil
}
func getSortFields(req *resource.ResourceSearchRequest) []string {
func getSortFields(req *resourcepb.ResourceSearchRequest) []string {
sorting := []string{}
for _, sort := range req.SortBy {
input := sort.Field
@@ -826,7 +828,7 @@ var termFields = []string{
}
// Convert a "requirement" into a bleve query
func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *resource.ErrorResult) {
func requirementQuery(req *resourcepb.Requirement, prefix string) (query.Query, *resourcepb.ErrorResult) {
switch selection.Operator(req.Operator) {
case selection.Equals, selection.DoubleEquals:
if len(req.Values) == 0 {
@@ -946,11 +948,11 @@ func filterValue(field string, v string) string {
return v
}
func (b *bleveIndex) hitsToTable(ctx context.Context, selectFields []string, hits search.DocumentMatchCollection, explain bool) (*resource.ResourceTable, error) {
func (b *bleveIndex) hitsToTable(ctx context.Context, selectFields []string, hits search.DocumentMatchCollection, explain bool) (*resourcepb.ResourceTable, error) {
_, span := b.tracing.Start(ctx, tracingPrexfixBleve+"hitsToTable")
defer span.End()
fields := []*resource.ResourceTableColumnDefinition{}
fields := []*resourcepb.ResourceTableColumnDefinition{}
for _, name := range selectFields {
if name == "_all" {
fields = b.allFields
@@ -964,9 +966,9 @@ func (b *bleveIndex) hitsToTable(ctx context.Context, selectFields []string, hit
if f == nil {
// Labels as a string
if strings.HasPrefix(name, "labels.") {
f = &resource.ResourceTableColumnDefinition{
f = &resourcepb.ResourceTableColumnDefinition{
Name: name,
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
}
}
@@ -987,18 +989,18 @@ func (b *bleveIndex) hitsToTable(ctx context.Context, selectFields []string, hit
}
encoders := builder.Encoders()
table := &resource.ResourceTable{
table := &resourcepb.ResourceTable{
Columns: fields,
Rows: make([]*resource.ResourceTableRow, hits.Len()),
Rows: make([]*resourcepb.ResourceTableRow, hits.Len()),
}
for rowID, match := range hits {
row := &resource.ResourceTableRow{
Key: &resource.ResourceKey{},
row := &resourcepb.ResourceTableRow{
Key: &resourcepb.ResourceKey{},
Cells: make([][]byte, len(fields)),
}
table.Rows[rowID] = row
err := row.Key.ReadSearchID(match.ID)
err := resource.ReadSearchID(row.Key, match.ID)
if err != nil {
return nil, err
}
@@ -1048,8 +1050,8 @@ func (b *bleveIndex) hitsToTable(ctx context.Context, selectFields []string, hit
return table, nil
}
func getAllFields(standard resource.SearchableDocumentFields, custom resource.SearchableDocumentFields) ([]*resource.ResourceTableColumnDefinition, error) {
fields := []*resource.ResourceTableColumnDefinition{
func getAllFields(standard resource.SearchableDocumentFields, custom resource.SearchableDocumentFields) ([]*resourcepb.ResourceTableColumnDefinition, error) {
fields := []*resourcepb.ResourceTableColumnDefinition{
standard.Field(resource.SEARCH_FIELD_ID),
standard.Field(resource.SEARCH_FIELD_TITLE),
standard.Field(resource.SEARCH_FIELD_TAGS),
@@ -1077,15 +1079,15 @@ func getAllFields(standard resource.SearchableDocumentFields, custom resource.Se
return fields, nil
}
func newResponseFacet(v *search.FacetResult) *resource.ResourceSearchResponse_Facet {
f := &resource.ResourceSearchResponse_Facet{
func newResponseFacet(v *search.FacetResult) *resourcepb.ResourceSearchResponse_Facet {
f := &resourcepb.ResourceSearchResponse_Facet{
Field: v.Field,
Total: int64(v.Total),
Missing: int64(v.Missing),
}
if v.Terms != nil {
for _, t := range v.Terms.Terms() {
f.Terms = append(f.Terms, &resource.ResourceSearchResponse_TermFacet{
f.Terms = append(f.Terms, &resourcepb.ResourceSearchResponse_TermFacet{
Term: t.Term,
Count: int64(t.Count),
})
@@ -9,6 +9,8 @@ import (
"time"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/stretchr/testify/require"
)
@@ -85,7 +87,7 @@ func BenchmarkBleveQuery(b *testing.B) {
}
func newTestWriter(size int, batchSize int) IndexWriter {
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Namespace: "default",
Group: "dashboard.grafana.app",
Resource: "dashboards",
@@ -105,7 +107,7 @@ func newTestWriter(size int, batchSize int) IndexWriter {
Doc: &resource.IndexableDocument{
RV: int64(i),
Name: name,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: name,
Namespace: key.Namespace,
Group: key.Group,
+29 -28
View File
@@ -16,13 +16,14 @@ import (
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
)
const threshold = 9999
func TestCanSearchByTitle(t *testing.T) {
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Namespace: "default",
Group: "dashboard.grafana.app",
Resource: "dashboards",
@@ -37,7 +38,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name1",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name1",
Namespace: key.Namespace,
Group: key.Group,
@@ -51,7 +52,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name2",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name2",
Namespace: key.Namespace,
Group: key.Group,
@@ -81,7 +82,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name1",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name1",
Namespace: key.Namespace,
Group: key.Group,
@@ -95,7 +96,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name2",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name2",
Namespace: key.Namespace,
Group: key.Group,
@@ -125,7 +126,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name1",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name1",
Namespace: key.Namespace,
Group: key.Group,
@@ -139,7 +140,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name2",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name2",
Namespace: key.Namespace,
Group: key.Group,
@@ -168,7 +169,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name1",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name1",
Namespace: key.Namespace,
Group: key.Group,
@@ -182,7 +183,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name2",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name2",
Namespace: key.Namespace,
Group: key.Group,
@@ -212,7 +213,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name1",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "aaa",
Namespace: key.Namespace,
Group: key.Group,
@@ -253,7 +254,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name1",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "aaa",
Namespace: key.Namespace,
Group: key.Group,
@@ -267,7 +268,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 2,
Name: "name2",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name2",
Namespace: key.Namespace,
Group: key.Group,
@@ -300,7 +301,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name1",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "aaa",
Namespace: key.Namespace,
Group: key.Group,
@@ -365,7 +366,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name1",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name1",
Namespace: key.Namespace,
Group: key.Group,
@@ -379,7 +380,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name2",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name2",
Namespace: key.Namespace,
Group: key.Group,
@@ -393,7 +394,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name3",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "name3",
Namespace: key.Namespace,
Group: key.Group,
@@ -434,7 +435,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "name1",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "aaa",
Namespace: key.Namespace,
Group: key.Group,
@@ -455,7 +456,7 @@ func TestCanSearchByTitle(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: int64(i),
Name: fmt.Sprintf("name%d", i),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: fmt.Sprintf("name%d", i),
Namespace: key.Namespace,
Group: key.Group,
@@ -491,10 +492,10 @@ func TestCanSearchByTitle(t *testing.T) {
})
}
func newTestQuery(query string) *resource.ResourceSearchRequest {
return &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
func newTestQuery(query string) *resourcepb.ResourceSearchRequest {
return &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: "dashboard.grafana.app",
Resource: "dashboards",
@@ -505,22 +506,22 @@ func newTestQuery(query string) *resource.ResourceSearchRequest {
}
}
func newQueryByTitle(query string) *resource.ResourceSearchRequest {
return &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
func newQueryByTitle(query string) *resourcepb.ResourceSearchRequest {
return &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: "dashboard.grafana.app",
Resource: "dashboards",
},
Fields: []*resource.Requirement{{Key: "title", Operator: "=", Values: []string{query}}},
Fields: []*resourcepb.Requirement{{Key: "title", Operator: "=", Values: []string{query}}},
},
Limit: 100000,
}
}
func newTestDashboardsIndex(t TB, threshold int64, size int64, batchSize int64, writer IndexWriter) (resource.ResourceIndex, string) {
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Namespace: "default",
Group: "dashboard.grafana.app",
Resource: "dashboards",
+51 -49
View File
@@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require"
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/tracing"
@@ -21,15 +22,16 @@ import (
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func TestBleveBackend(t *testing.T) {
dashboardskey := &resource.ResourceKey{
dashboardskey := &resourcepb.ResourceKey{
Namespace: "default",
Group: "dashboard.grafana.app",
Resource: "dashboards",
}
folderKey := &resource.ResourceKey{
folderKey := &resourcepb.ResourceKey{
Namespace: dashboardskey.Namespace,
Group: "folder.grafana.app",
Resource: "folders",
@@ -72,7 +74,7 @@ func TestBleveBackend(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 1,
Name: "aaa",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "aaa",
Namespace: "ns",
Group: "dashboard.grafana.app",
@@ -105,7 +107,7 @@ func TestBleveBackend(t *testing.T) {
Doc: &resource.IndexableDocument{
RV: 2,
Name: "bbb",
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "bbb",
Namespace: "ns",
Group: "dashboard.grafana.app",
@@ -138,7 +140,7 @@ func TestBleveBackend(t *testing.T) {
Action: resource.ActionIndex,
Doc: &resource.IndexableDocument{
RV: 3,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "ccc",
Namespace: "ns",
Group: "dashboard.grafana.app",
@@ -172,15 +174,15 @@ func TestBleveBackend(t *testing.T) {
require.NotNil(t, index)
dashboardsIndex = index
rsp, err := index.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true}), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
rsp, err := index.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true}), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: key,
},
Limit: 100000,
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: resource.SEARCH_FIELD_TITLE, Desc: true}, // ccc,bbb,aaa
},
Facet: map[string]*resource.ResourceSearchRequest_Facet{
Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{
"tags": {
Field: "tags",
Limit: 100,
@@ -221,10 +223,10 @@ func TestBleveBackend(t *testing.T) {
count, _ = index.DocCount(ctx, "zzz")
assert.Equal(t, int64(1), count)
rsp, err = index.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true}), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
rsp, err = index.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true}), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: key,
Labels: []*resource.Requirement{{
Labels: []*resourcepb.Requirement{{
Key: utils.LabelKeyDeprecatedInternalID, // nolint:staticcheck
Operator: "in",
Values: []string{"10", "11"},
@@ -240,13 +242,13 @@ func TestBleveBackend(t *testing.T) {
})
// can get sprinkles fields and sort by them
rsp, err = index.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true}), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
rsp, err = index.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true}), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: key,
},
Limit: 100000,
Fields: []string{DASHBOARD_ERRORS_TODAY, DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"},
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "fields." + DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true},
},
}, nil)
@@ -260,13 +262,13 @@ func TestBleveBackend(t *testing.T) {
require.Equal(t, int64(100), val)
// check auth will exclude results we don't have access to
rsp, err = index.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": false}), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
rsp, err = index.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": false}), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: key,
},
Limit: 100000,
Fields: []string{DASHBOARD_ERRORS_TODAY, DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"},
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "fields." + DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true},
},
}, nil)
@@ -274,7 +276,7 @@ func TestBleveBackend(t *testing.T) {
require.Equal(t, 0, len(rsp.Results.Rows))
// Now look for repositories
found, err := index.ListManagedObjects(ctx, &resource.ListManagedObjectsRequest{
found, err := index.ListManagedObjects(ctx, &resourcepb.ListManagedObjectsRequest{
Kind: "repo",
Id: "repo-1",
})
@@ -352,7 +354,7 @@ func TestBleveBackend(t *testing.T) {
Action: resource.ActionIndex,
Doc: &resource.IndexableDocument{
RV: 1,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "zzz",
Namespace: "ns",
Group: "folder.grafana.app",
@@ -377,7 +379,7 @@ func TestBleveBackend(t *testing.T) {
Action: resource.ActionIndex,
Doc: &resource.IndexableDocument{
RV: 2,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Name: "yyy",
Namespace: "ns",
Group: "folder.grafana.app",
@@ -401,8 +403,8 @@ func TestBleveBackend(t *testing.T) {
require.NotNil(t, index)
foldersIndex = index
rsp, err := index.Search(ctx, NewStubAccessClient(map[string]bool{"folders": true}), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
rsp, err := index.Search(ctx, NewStubAccessClient(map[string]bool{"folders": true}), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: key,
},
Limit: 100000,
@@ -421,21 +423,21 @@ func TestBleveBackend(t *testing.T) {
require.NotNil(t, foldersIndex)
// Use a federated query to get both results together, sorted by title
rsp, err := dashboardsIndex.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true, "folders": true}), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
rsp, err := dashboardsIndex.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true, "folders": true}), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardskey,
},
Fields: []string{
"title", "_id",
},
Federated: []*resource.ResourceKey{
Federated: []*resourcepb.ResourceKey{
folderKey, // This will join in the
},
Limit: 100000,
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "title", Desc: false},
},
Facet: map[string]*resource.ResourceSearchRequest_Facet{
Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{
"region": {
Field: "labels.region",
Limit: 100,
@@ -485,21 +487,21 @@ func TestBleveBackend(t *testing.T) {
}`, string(disp))
// now only when we have permissions to see dashboards
rsp, err = dashboardsIndex.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true, "folders": false}), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
rsp, err = dashboardsIndex.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": true, "folders": false}), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardskey,
},
Fields: []string{
"title", "_id",
},
Federated: []*resource.ResourceKey{
Federated: []*resourcepb.ResourceKey{
folderKey, // This will join in the
},
Limit: 100000,
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "title", Desc: false},
},
Facet: map[string]*resource.ResourceSearchRequest_Facet{
Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{
"region": {
Field: "labels.region",
Limit: 100,
@@ -514,21 +516,21 @@ func TestBleveBackend(t *testing.T) {
require.Equal(t, "dashboards", rsp.Results.Rows[2].Key.Resource)
// now only when we have permissions to see folders
rsp, err = dashboardsIndex.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": false, "folders": true}), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
rsp, err = dashboardsIndex.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": false, "folders": true}), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardskey,
},
Fields: []string{
"title", "_id",
},
Federated: []*resource.ResourceKey{
Federated: []*resourcepb.ResourceKey{
folderKey, // This will join in the
},
Limit: 100000,
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "title", Desc: false},
},
Facet: map[string]*resource.ResourceSearchRequest_Facet{
Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{
"region": {
Field: "labels.region",
Limit: 100,
@@ -542,21 +544,21 @@ func TestBleveBackend(t *testing.T) {
require.Equal(t, "folders", rsp.Results.Rows[1].Key.Resource)
// now when we have permissions to see nothing
rsp, err = dashboardsIndex.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": false, "folders": false}), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
rsp, err = dashboardsIndex.Search(ctx, NewStubAccessClient(map[string]bool{"dashboards": false, "folders": false}), &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: dashboardskey,
},
Fields: []string{
"title", "_id",
},
Federated: []*resource.ResourceKey{
Federated: []*resourcepb.ResourceKey{
folderKey, // This will join in the
},
Limit: 100000,
SortBy: []*resource.ResourceSearchRequest_Sort{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "title", Desc: false},
},
Facet: map[string]*resource.ResourceSearchRequest_Facet{
Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{
"region": {
Field: "labels.region",
Limit: 100,
@@ -571,8 +573,8 @@ func TestBleveBackend(t *testing.T) {
func TestGetSortFields(t *testing.T) {
t.Run("will prepend 'fields.' to sort fields when they are dashboard fields", func(t *testing.T) {
searchReq := &resource.ResourceSearchRequest{
SortBy: []*resource.ResourceSearchRequest_Sort{
searchReq := &resourcepb.ResourceSearchRequest{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "views_total", Desc: false},
},
}
@@ -580,8 +582,8 @@ func TestGetSortFields(t *testing.T) {
assert.Equal(t, []string{"fields.views_total"}, sortFields)
})
t.Run("will prepend sort fields with a '-' when sort is Desc", func(t *testing.T) {
searchReq := &resource.ResourceSearchRequest{
SortBy: []*resource.ResourceSearchRequest_Sort{
searchReq := &resourcepb.ResourceSearchRequest{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "views_total", Desc: true},
},
}
@@ -589,8 +591,8 @@ func TestGetSortFields(t *testing.T) {
assert.Equal(t, []string{"-fields.views_total"}, sortFields)
})
t.Run("will not prepend 'fields.' to common fields", func(t *testing.T) {
searchReq := &resource.ResourceSearchRequest{
SortBy: []*resource.ResourceSearchRequest_Sort{
searchReq := &resourcepb.ResourceSearchRequest{
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "description", Desc: false},
},
}
+38 -37
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
//------------------------------------------------------------
@@ -45,146 +46,146 @@ const DASHBOARD_ERRORS_TOTAL = "errors_total"
const DASHBOARD_ERRORS_TODAY = "errors_today"
func DashboardBuilder(namespaced resource.NamespacedDocumentSupplier) (resource.DocumentBuilderInfo, error) {
fields, err := resource.NewSearchableDocumentFields([]*resource.ResourceTableColumnDefinition{
fields, err := resource.NewSearchableDocumentFields([]*resourcepb.ResourceTableColumnDefinition{
{
Name: DASHBOARD_SCHEMA_VERSION,
Type: resource.ResourceTableColumnDefinition_INT32,
Type: resourcepb.ResourceTableColumnDefinition_INT32,
Description: "Numeric version saying when the schema was saved",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
NotNull: true,
},
},
{
Name: DASHBOARD_LINK_COUNT,
Type: resource.ResourceTableColumnDefinition_INT32,
Type: resourcepb.ResourceTableColumnDefinition_INT32,
Description: "How many links appear on the page",
},
{
Name: DASHBOARD_PANEL_TYPES,
Type: resource.ResourceTableColumnDefinition_STRING,
Type: resourcepb.ResourceTableColumnDefinition_STRING,
IsArray: true,
Description: "How many links appear on the page",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_ERRORS_TODAY,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of errors that occurred today",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_ERRORS_LAST_1_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of errors that occurred in the last 1 days",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_ERRORS_LAST_7_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of errors that occurred in the last 7 days",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_ERRORS_LAST_30_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of errors that occurred in the last 30 days",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_ERRORS_TOTAL,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Total number of errors",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_QUERIES_TODAY,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of queries that occurred today",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_QUERIES_LAST_1_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of queries that occurred in the last 1 days",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_QUERIES_LAST_7_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of queries that occurred in the last 7 days",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_QUERIES_LAST_30_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of queries that occurred in the last 30 days",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_QUERIES_TOTAL,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Total number of queries",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_VIEWS_TODAY,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of views that occurred today",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_VIEWS_LAST_1_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of views that occurred in the last 1 days",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_VIEWS_LAST_7_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of views that occurred in the last 7 days",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_VIEWS_LAST_30_DAYS,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Number of views that occurred in the last 30 days",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
{
Name: DASHBOARD_VIEWS_TOTAL,
Type: resource.ResourceTableColumnDefinition_INT64,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
Description: "Total number of views",
Properties: &resource.ResourceTableColumnDefinition_Properties{
Properties: &resourcepb.ResourceTableColumnDefinition_Properties{
Filterable: true,
},
},
@@ -231,7 +232,7 @@ type DashboardStatsLookup = func(ctx context.Context, uid string) map[string]int
var _ resource.DocumentBuilder = &DashboardDocumentBuilder{}
func (s *DashboardDocumentBuilder) BuildDocument(ctx context.Context, key *resource.ResourceKey, rv int64, value []byte) (*resource.IndexableDocument, error) {
func (s *DashboardDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*resource.IndexableDocument, error) {
if s.Namespace != "" && s.Namespace != key.Namespace {
return nil, fmt.Errorf("invalid namespace")
}
+6 -5
View File
@@ -13,10 +13,11 @@ import (
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
)
func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string, key *resource.ResourceKey, names []string) {
func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string, key *resourcepb.ResourceKey, names []string) {
t.Helper()
for _, name := range names {
@@ -47,7 +48,7 @@ func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string
}
func TestDashboardDocumentBuilder(t *testing.T) {
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Namespace: "default",
Group: "dashboard.grafana.app",
Resource: "dashboards",
@@ -82,7 +83,7 @@ func TestDashboardDocumentBuilder(t *testing.T) {
// Standard
builder = resource.StandardDocumentBuilder()
doSnapshotTests(t, builder, "folder", &resource.ResourceKey{
doSnapshotTests(t, builder, "folder", &resourcepb.ResourceKey{
Namespace: "default",
Group: "folder.grafana.app",
Resource: "folders",
@@ -90,14 +91,14 @@ func TestDashboardDocumentBuilder(t *testing.T) {
"aaa",
"bbb",
})
doSnapshotTests(t, builder, "playlist", &resource.ResourceKey{
doSnapshotTests(t, builder, "playlist", &resourcepb.ResourceKey{
Namespace: "default",
Group: "playlist.grafana.app",
Resource: "playlists",
}, []string{
"aaa",
})
doSnapshotTests(t, builder, "report", &resource.ResourceKey{
doSnapshotTests(t, builder, "report", &resourcepb.ResourceKey{
Namespace: "default",
Group: "reporting.grafana.app",
Resource: "reports",
+24 -23
View File
@@ -23,6 +23,7 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
"github.com/grafana/grafana/pkg/storage/unified/sql/dbutil"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
@@ -36,7 +37,7 @@ const defaultPrunerHistoryLimit = 20
type Backend interface {
resource.StorageBackend
resource.DiagnosticsServer
resourcepb.DiagnosticsServer
resource.LifecycleHooks
}
@@ -218,7 +219,7 @@ func (b *backend) initPruner(ctx context.Context) error {
res, err := dbutil.Exec(ctx, tx, sqlResourceHistoryPrune, &sqlPruneHistoryRequest{
SQLTemplate: sqltemplate.New(b.dialect),
HistoryLimit: defaultPrunerHistoryLimit,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: key.namespace,
Group: key.group,
Resource: key.resource,
@@ -260,14 +261,14 @@ func (b *backend) initPruner(ctx context.Context) error {
return nil
}
func (b *backend) IsHealthy(ctx context.Context, r *resource.HealthCheckRequest) (*resource.HealthCheckResponse, error) {
func (b *backend) IsHealthy(ctx context.Context, _ *resourcepb.HealthCheckRequest) (*resourcepb.HealthCheckResponse, error) {
// ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "isHealthy"}))
if err := b.db.PingContext(ctx); err != nil {
return nil, err
}
return &resource.HealthCheckResponse{Status: resource.HealthCheckResponse_SERVING}, nil
return &resourcepb.HealthCheckResponse{Status: resourcepb.HealthCheckResponse_SERVING}, nil
}
func (b *backend) Stop(_ context.Context) error {
@@ -313,11 +314,11 @@ func (b *backend) WriteEvent(ctx context.Context, event resource.WriteEvent) (in
defer span.End()
// TODO: validate key ?
switch event.Type {
case resource.WatchEvent_ADDED:
case resourcepb.WatchEvent_ADDED:
return b.create(ctx, event)
case resource.WatchEvent_MODIFIED:
case resourcepb.WatchEvent_MODIFIED:
return b.update(ctx, event)
case resource.WatchEvent_DELETED:
case resourcepb.WatchEvent_DELETED:
return b.delete(ctx, event)
default:
return 0, fmt.Errorf("unsupported event type")
@@ -525,7 +526,7 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64,
return rv, nil
}
func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) *resource.BackendReadResponse {
func (b *backend) ReadResource(ctx context.Context, req *resourcepb.ReadRequest) *resource.BackendReadResponse {
_, span := b.tracer.Start(ctx, tracePrefix+".Read")
defer span.End()
@@ -558,7 +559,7 @@ func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) *
return res
}
func (b *backend) ListIterator(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
func (b *backend) ListIterator(ctx context.Context, req *resourcepb.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
ctx, span := b.tracer.Start(ctx, tracePrefix+"List")
defer span.End()
@@ -570,7 +571,7 @@ func (b *backend) ListIterator(ctx context.Context, req *resource.ListRequest, c
return 0, fmt.Errorf("missing group or resource")
}
if req.Source != resource.ListRequest_STORE {
if req.Source != resourcepb.ListRequest_STORE {
return b.getHistory(ctx, req, cb)
}
@@ -652,7 +653,7 @@ func (l *listIter) Next() bool {
var _ resource.ListIterator = (*listIter)(nil)
// listLatest fetches the resources from the resource table.
func (b *backend) listLatest(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
func (b *backend) listLatest(ctx context.Context, req *resourcepb.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
ctx, span := b.tracer.Start(ctx, tracePrefix+"listLatest")
defer span.End()
@@ -673,9 +674,9 @@ func (b *backend) listLatest(ctx context.Context, req *resource.ListRequest, cb
listReq := sqlResourceListRequest{
SQLTemplate: sqltemplate.New(b.dialect),
Request: new(resource.ListRequest),
Request: new(resourcepb.ListRequest),
}
listReq.Request = proto.Clone(req).(*resource.ListRequest)
listReq.Request = proto.Clone(req).(*resourcepb.ListRequest)
rows, err := dbutil.QueryRows(ctx, tx, sqlResourceList, listReq)
if rows != nil {
@@ -696,7 +697,7 @@ func (b *backend) listLatest(ctx context.Context, req *resource.ListRequest, cb
}
// listAtRevision fetches the resources from the resource_history table at a specific revision.
func (b *backend) listAtRevision(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
func (b *backend) listAtRevision(ctx context.Context, req *resourcepb.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
ctx, span := b.tracer.Start(ctx, tracePrefix+"listAtRevision")
defer span.End()
@@ -756,7 +757,7 @@ func (b *backend) listAtRevision(ctx context.Context, req *resource.ListRequest,
}
// readHistory fetches the resource history from the resource_history table.
func (b *backend) readHistory(ctx context.Context, key *resource.ResourceKey, rv int64) *resource.BackendReadResponse {
func (b *backend) readHistory(ctx context.Context, key *resourcepb.ResourceKey, rv int64) *resource.BackendReadResponse {
_, span := b.tracer.Start(ctx, tracePrefix+".ReadHistory")
defer span.End()
@@ -787,19 +788,19 @@ func (b *backend) readHistory(ctx context.Context, key *resource.ResourceKey, rv
}
// getHistory fetches the resource history from the resource_history table.
func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
func (b *backend) getHistory(ctx context.Context, req *resourcepb.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
ctx, span := b.tracer.Start(ctx, tracePrefix+"getHistory")
defer span.End()
listReq := sqlGetHistoryRequest{
SQLTemplate: sqltemplate.New(b.dialect),
Key: req.Options.Key,
Trash: req.Source == resource.ListRequest_TRASH,
Trash: req.Source == resourcepb.ListRequest_TRASH,
}
// We are assuming that users want history in ascending order
// when they are using NotOlderThan matching, and descending order
// for Unset (default) and Exact matching.
listReq.SortAscending = req.GetVersionMatchV2() == resource.ResourceVersionMatchV2_NotOlderThan
listReq.SortAscending = req.GetVersionMatchV2() == resourcepb.ResourceVersionMatchV2_NotOlderThan
iter := &listIter{}
if req.NextPageToken != "" {
@@ -813,7 +814,7 @@ func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb
iter.sortAsc = listReq.SortAscending
// Set ExactRV when using Exact matching
if req.VersionMatchV2 == resource.ResourceVersionMatchV2_Exact {
if req.VersionMatchV2 == resourcepb.ResourceVersionMatchV2_Exact {
if req.ResourceVersion <= 0 {
return 0, fmt.Errorf("expecting an explicit resource version query when using Exact matching")
}
@@ -821,12 +822,12 @@ func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb
}
// Set MinRV when using NotOlderThan matching to filter at the database level
if req.ResourceVersion > 0 && req.VersionMatchV2 == resource.ResourceVersionMatchV2_NotOlderThan {
if req.ResourceVersion > 0 && req.VersionMatchV2 == resourcepb.ResourceVersionMatchV2_NotOlderThan {
listReq.MinRV = req.ResourceVersion
}
// Ignore last deleted history record when listing the trash, using exact matching or not older than matching with a specific RV
useLatestDeletionAsMinRV := listReq.MinRV == 0 && !listReq.Trash && req.VersionMatchV2 != resource.ResourceVersionMatchV2_Exact
useLatestDeletionAsMinRV := listReq.MinRV == 0 && !listReq.Trash && req.VersionMatchV2 != resourcepb.ResourceVersionMatchV2_Exact
err := b.db.WithTx(ctx, ReadCommittedRO, func(ctx context.Context, tx db.Tx) error {
var err error
@@ -836,7 +837,7 @@ func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb
}
if useLatestDeletionAsMinRV {
latestDeletedRV, err := b.fetchLatestHistoryRV(ctx, tx, b.dialect, req.Options.Key, resource.WatchEvent_DELETED)
latestDeletedRV, err := b.fetchLatestHistoryRV(ctx, tx, b.dialect, req.Options.Key, resourcepb.WatchEvent_DELETED)
if err != nil {
return err
}
@@ -914,7 +915,7 @@ func (b *backend) fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqlte
}
// fetchLatestHistoryRV returns the current maximum RV in the resource_history table
func (b *backend) fetchLatestHistoryRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, key *resource.ResourceKey, eventType resource.WatchEvent_Type) (int64, error) {
func (b *backend) fetchLatestHistoryRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, key *resourcepb.ResourceKey, eventType resourcepb.WatchEvent_Type) (int64, error) {
ctx, span := b.tracer.Start(ctx, tracePrefix+"fetchLatestHistoryRV")
defer span.End()
res, err := dbutil.QueryRow(ctx, x, sqlResourceHistoryReadLatestRV, sqlResourceHistoryReadLatestRVRequest{
+33 -32
View File
@@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl"
"github.com/grafana/grafana/pkg/storage/unified/sql/test"
"github.com/grafana/grafana/pkg/util/testutil"
@@ -22,7 +23,7 @@ import (
var (
errTest = errors.New("things happened")
resKey = &resource.ResourceKey{
resKey = &resourcepb.ResourceKey{
Namespace: "ns",
Group: "gr",
Resource: "rs",
@@ -212,7 +213,7 @@ func TestBackend_create(t *testing.T) {
})
require.NoError(t, err)
event := resource.WriteEvent{
Type: resource.WatchEvent_ADDED,
Type: resourcepb.WatchEvent_ADDED,
Key: resKey,
Object: meta,
}
@@ -292,7 +293,7 @@ func TestBackend_update(t *testing.T) {
require.NoError(t, err)
meta.SetFolder("folderuid")
event := resource.WriteEvent{
Type: resource.WatchEvent_MODIFIED,
Type: resourcepb.WatchEvent_MODIFIED,
Key: resKey,
Object: meta,
}
@@ -350,7 +351,7 @@ func TestBackend_delete(t *testing.T) {
})
require.NoError(t, err)
event := resource.WriteEvent{
Type: resource.WatchEvent_DELETED,
Type: resourcepb.WatchEvent_DELETED,
Key: resKey,
Object: meta,
}
@@ -445,7 +446,7 @@ func TestBackend_ReadResource(t *testing.T) {
))
b.SQLMock.ExpectCommit()
req := &resource.ReadRequest{
req := &resourcepb.ReadRequest{
Key: resKey,
}
rps := b.ReadResource(ctx, req)
@@ -464,7 +465,7 @@ func TestBackend_ReadResource(t *testing.T) {
WillReturnRows(sqlmock.NewRows([]string{}))
b.SQLMock.ExpectCommit()
req := &resource.ReadRequest{
req := &resourcepb.ReadRequest{
Key: resKey,
}
res := b.ReadResource(ctx, req)
@@ -503,7 +504,7 @@ func TestBackend_ReadResource(t *testing.T) {
))
b.SQLMock.ExpectCommit()
req := &resource.ReadRequest{
req := &resourcepb.ReadRequest{
Key: resKey,
ResourceVersion: 300,
}
@@ -521,7 +522,7 @@ func TestBackend_ReadResource(t *testing.T) {
b.SQLMock.ExpectQuery("SELECT .* FROM resource_history").
WillReturnError(errTest)
req := &resource.ReadRequest{
req := &resourcepb.ReadRequest{
Key: resKey,
}
rps := b.ReadResource(ctx, req)
@@ -533,7 +534,7 @@ func TestBackend_getHistory(t *testing.T) {
t.Parallel()
// Common setup
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Namespace: "ns",
Group: "gr",
Resource: "rs",
@@ -544,8 +545,8 @@ func TestBackend_getHistory(t *testing.T) {
tests := []struct {
name string
source resource.ListRequest_Source
versionMatch resource.ResourceVersionMatchV2
source resourcepb.ListRequest_Source
versionMatch resourcepb.ResourceVersionMatchV2
resourceVersion int64
expectedVersions []int64
expectedListRv int64
@@ -555,8 +556,8 @@ func TestBackend_getHistory(t *testing.T) {
}{
{
name: "with ResourceVersionMatch_NotOlderThan",
source: resource.ListRequest_HISTORY,
versionMatch: resource.ResourceVersionMatchV2_NotOlderThan,
source: resourcepb.ListRequest_HISTORY,
versionMatch: resourcepb.ResourceVersionMatchV2_NotOlderThan,
resourceVersion: rv2,
expectedVersions: []int64{rv2, rv3}, // Should be in ASC order due to NotOlderThan
expectedListRv: rv3,
@@ -564,8 +565,8 @@ func TestBackend_getHistory(t *testing.T) {
},
{
name: "with ResourceVersionMatch_NotOlderThan and ResourceVersion=0",
source: resource.ListRequest_HISTORY,
versionMatch: resource.ResourceVersionMatchV2_NotOlderThan,
source: resourcepb.ListRequest_HISTORY,
versionMatch: resourcepb.ResourceVersionMatchV2_NotOlderThan,
resourceVersion: 0,
expectedVersions: []int64{rv1, rv2, rv3}, // Should be in ASC order due to NotOlderThan
expectedListRv: rv3,
@@ -574,8 +575,8 @@ func TestBackend_getHistory(t *testing.T) {
},
{
name: "with ResourceVersionMatch_Exact",
source: resource.ListRequest_HISTORY,
versionMatch: resource.ResourceVersionMatchV2_Exact,
source: resourcepb.ListRequest_HISTORY,
versionMatch: resourcepb.ResourceVersionMatchV2_Exact,
resourceVersion: rv2,
expectedVersions: []int64{rv2},
expectedListRv: rv3,
@@ -583,7 +584,7 @@ func TestBackend_getHistory(t *testing.T) {
},
{
name: "with ResourceVersionMatch_Unset (default)",
source: resource.ListRequest_HISTORY,
source: resourcepb.ListRequest_HISTORY,
expectedVersions: []int64{rv3, rv2, rv1}, // Should be in DESC order by default
expectedListRv: rv3,
expectedRowsCount: 3,
@@ -591,14 +592,14 @@ func TestBackend_getHistory(t *testing.T) {
},
{
name: "error with ResourceVersionMatch_Exact and ResourceVersion <= 0",
source: resource.ListRequest_HISTORY,
versionMatch: resource.ResourceVersionMatchV2_Exact,
source: resourcepb.ListRequest_HISTORY,
versionMatch: resourcepb.ResourceVersionMatchV2_Exact,
resourceVersion: 0,
expectedErr: "expecting an explicit resource version query when using Exact matching",
},
{
name: "with ListRequest_TRASH",
source: resource.ListRequest_TRASH,
source: resourcepb.ListRequest_TRASH,
expectedVersions: []int64{rv3, rv2, rv1}, // Should be in DESC order by default
expectedListRv: rv3,
expectedRowsCount: 3,
@@ -612,8 +613,8 @@ func TestBackend_getHistory(t *testing.T) {
b, ctx := setupBackendTest(t)
// Build request with appropriate matcher
req := &resource.ListRequest{
Options: &resource.ListOptions{Key: key},
req := &resourcepb.ListRequest{
Options: &resourcepb.ListOptions{Key: key},
ResourceVersion: tc.resourceVersion,
VersionMatchV2: tc.versionMatch,
Source: tc.source,
@@ -700,7 +701,7 @@ func TestBackend_getHistoryPagination(t *testing.T) {
t.Parallel()
// Common setup
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Namespace: "ns",
Group: "gr",
Resource: "rs",
@@ -749,11 +750,11 @@ func TestBackend_getHistoryPagination(t *testing.T) {
// Test each page
for _, page := range pages {
req := &resource.ListRequest{
Options: &resource.ListOptions{Key: key},
req := &resourcepb.ListRequest{
Options: &resourcepb.ListOptions{Key: key},
ResourceVersion: initialRV,
VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan,
Source: resource.ListRequest_HISTORY,
VersionMatchV2: resourcepb.ResourceVersionMatchV2_NotOlderThan,
Source: resourcepb.ListRequest_HISTORY,
Limit: 4,
}
if page.token != nil {
@@ -790,11 +791,11 @@ func TestBackend_getHistoryPagination(t *testing.T) {
t.Run("pagination with ResourceVersion=0 and NotOlderThan should return entries in ASC order", func(t *testing.T) {
b, ctx := setupBackendTest(t)
req := &resource.ListRequest{
Options: &resource.ListOptions{Key: key},
req := &resourcepb.ListRequest{
Options: &resourcepb.ListOptions{Key: key},
ResourceVersion: 0,
VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan,
Source: resource.ListRequest_HISTORY,
VersionMatchV2: resourcepb.ResourceVersionMatchV2_NotOlderThan,
Source: resourcepb.ListRequest_HISTORY,
Limit: 4,
}
+12 -11
View File
@@ -1,7 +1,7 @@
package sql
import (
context "context"
"context"
"crypto/md5"
"encoding/hex"
"fmt"
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
"github.com/grafana/grafana/pkg/storage/unified/sql/dbutil"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
@@ -25,12 +26,12 @@ func (b *backend) SupportsSignedURLs() bool {
return false
}
func (b *backend) PutResourceBlob(ctx context.Context, req *resource.PutBlobRequest) (*resource.PutBlobResponse, error) {
func (b *backend) PutResourceBlob(ctx context.Context, req *resourcepb.PutBlobRequest) (*resourcepb.PutBlobResponse, error) {
ctx, span := b.tracer.Start(ctx, tracePrefix+"PutResourceBlob")
defer span.End()
if req.Method == resource.PutBlobRequest_HTTP {
return &resource.PutBlobResponse{
if req.Method == resourcepb.PutBlobRequest_HTTP {
return &resourcepb.PutBlobResponse{
Error: resource.NewBadRequestError("signed url upload not supported"),
}, nil
}
@@ -49,7 +50,7 @@ func (b *backend) PutResourceBlob(ctx context.Context, req *resource.PutBlobRequ
info.SetContentType(req.ContentType)
if info.Size < 1 {
return &resource.PutBlobResponse{
return &resourcepb.PutBlobResponse{
Error: resource.NewBadRequestError("empty content"),
}, nil
}
@@ -68,11 +69,11 @@ func (b *backend) PutResourceBlob(ctx context.Context, req *resource.PutBlobRequ
})
if err != nil {
return &resource.PutBlobResponse{
return &resourcepb.PutBlobResponse{
Error: resource.AsErrorResult(err),
}, nil
}
return &resource.PutBlobResponse{
return &resourcepb.PutBlobResponse{
Uid: info.UID,
Size: info.Size,
MimeType: info.MimeType,
@@ -81,17 +82,17 @@ func (b *backend) PutResourceBlob(ctx context.Context, req *resource.PutBlobRequ
}, nil
}
func (b *backend) GetResourceBlob(ctx context.Context, key *resource.ResourceKey, info *utils.BlobInfo, mustProxy bool) (*resource.GetBlobResponse, error) {
func (b *backend) GetResourceBlob(ctx context.Context, key *resourcepb.ResourceKey, info *utils.BlobInfo, mustProxy bool) (*resourcepb.GetBlobResponse, error) {
ctx, span := b.tracer.Start(ctx, tracePrefix+"GetResourceBlob")
defer span.End()
if info == nil {
return &resource.GetBlobResponse{
return &resourcepb.GetBlobResponse{
Error: resource.NewBadRequestError("missing blob info"),
}, nil
}
rsp := &resource.GetBlobResponse{}
rsp := &resourcepb.GetBlobResponse{}
err := b.db.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error {
rows, err := dbutil.QueryRows(ctx, tx, sqlResourceBlobQuery, sqlResourceBlobQueryRequest{
SQLTemplate: sqltemplate.New(b.dialect),
@@ -109,7 +110,7 @@ func (b *backend) GetResourceBlob(ctx context.Context, key *resource.ResourceKey
}
return err
}
rsp.Error = &resource.ErrorResult{
rsp.Error = &resourcepb.ErrorResult{
Code: http.StatusNotFound,
}
return err
+24 -22
View File
@@ -14,9 +14,11 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/parquet"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
"github.com/grafana/grafana/pkg/storage/unified/sql/dbutil"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
@@ -62,14 +64,14 @@ type bulkLock struct {
mu sync.Mutex
}
func (x *bulkLock) Start(keys []*resource.ResourceKey) error {
func (x *bulkLock) Start(keys []*resourcepb.ResourceKey) error {
x.mu.Lock()
defer x.mu.Unlock()
// First verify that it is not already running
ids := make([]string, len(keys))
for i, k := range keys {
id := k.NSGR()
id := resource.NSGR(k)
if x.running[id] {
return &apierrors.StatusError{ErrStatus: metav1.Status{
Code: http.StatusPreconditionFailed,
@@ -86,11 +88,11 @@ func (x *bulkLock) Start(keys []*resource.ResourceKey) error {
return nil
}
func (x *bulkLock) Finish(keys []*resource.ResourceKey) {
func (x *bulkLock) Finish(keys []*resourcepb.ResourceKey) {
x.mu.Lock()
defer x.mu.Unlock()
for _, k := range keys {
delete(x.running, k.NSGR())
delete(x.running, resource.NSGR(k))
}
}
@@ -100,10 +102,10 @@ func (x *bulkLock) Active() bool {
return len(x.running) > 0
}
func (b *backend) ProcessBulk(ctx context.Context, setting resource.BulkSettings, iter resource.BulkRequestIterator) *resource.BulkResponse {
func (b *backend) ProcessBulk(ctx context.Context, setting resource.BulkSettings, iter resource.BulkRequestIterator) *resourcepb.BulkResponse {
err := b.bulkLock.Start(setting.Collection)
if err != nil {
return &resource.BulkResponse{
return &resourcepb.BulkResponse{
Error: resource.AsErrorResult(err),
}
}
@@ -113,14 +115,14 @@ func (b *backend) ProcessBulk(ctx context.Context, setting resource.BulkSettings
if b.dialect.DialectName() == "sqlite" {
file, err := os.CreateTemp("", "grafana-bulk-export-*.parquet")
if err != nil {
return &resource.BulkResponse{
return &resourcepb.BulkResponse{
Error: resource.AsErrorResult(err),
}
}
writer, err := parquet.NewParquetWriter(file)
if err != nil {
return &resource.BulkResponse{
return &resourcepb.BulkResponse{
Error: resource.AsErrorResult(err),
}
}
@@ -136,7 +138,7 @@ func (b *backend) ProcessBulk(ctx context.Context, setting resource.BulkSettings
// Replace the iterator with one from parquet
iter, err = parquet.NewParquetReader(file.Name(), 50)
if err != nil {
return &resource.BulkResponse{
return &resourcepb.BulkResponse{
Error: resource.AsErrorResult(err),
}
}
@@ -146,8 +148,8 @@ func (b *backend) ProcessBulk(ctx context.Context, setting resource.BulkSettings
}
// internal bulk process
func (b *backend) processBulk(ctx context.Context, setting resource.BulkSettings, iter resource.BulkRequestIterator) *resource.BulkResponse {
rsp := &resource.BulkResponse{}
func (b *backend) processBulk(ctx context.Context, setting resource.BulkSettings, iter resource.BulkRequestIterator) *resourcepb.BulkResponse {
rsp := &resourcepb.BulkResponse{}
err := b.db.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error {
rollbackWithError := func(err error) error {
txerr := tx.Rollback()
@@ -168,7 +170,7 @@ func (b *backend) processBulk(ctx context.Context, setting resource.BulkSettings
// Calculate the RV based on incoming request timestamps
rv := newBulkRV()
summaries := make(map[string]*resource.BulkResponse_Summary, len(setting.Collection)*4)
summaries := make(map[string]*resourcepb.BulkResponse_Summary, len(setting.Collection)*4)
// First clear everything in the transaction
if setting.RebuildCollection {
@@ -177,7 +179,7 @@ func (b *backend) processBulk(ctx context.Context, setting resource.BulkSettings
if err != nil {
return rollbackWithError(err)
}
summaries[key.NSGR()] = summary
summaries[resource.NSGR(key)] = summary
rsp.Summary = append(rsp.Summary, summary)
}
}
@@ -195,8 +197,8 @@ func (b *backend) processBulk(ctx context.Context, setting resource.BulkSettings
}
rsp.Processed++
if req.Action == resource.BulkRequest_UNKNOWN {
rsp.Rejected = append(rsp.Rejected, &resource.BulkResponse_Rejected{
if req.Action == resourcepb.BulkRequest_UNKNOWN {
rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{
Key: req.Key,
Action: req.Action,
Error: "unknown action",
@@ -206,7 +208,7 @@ func (b *backend) processBulk(ctx context.Context, setting resource.BulkSettings
err := obj.UnmarshalJSON(req.Value)
if err != nil {
rsp.Rejected = append(rsp.Rejected, &resource.BulkResponse_Rejected{
rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{
Key: req.Key,
Action: req.Action,
Error: "unable to unmarshal json",
@@ -219,7 +221,7 @@ func (b *backend) processBulk(ctx context.Context, setting resource.BulkSettings
SQLTemplate: sqltemplate.New(b.dialect),
WriteEvent: resource.WriteEvent{
Key: req.Key,
Type: resource.WatchEvent_Type(req.Action),
Type: resourcepb.WatchEvent_Type(req.Action),
Value: req.Value,
PreviousRV: -1, // Used for WATCH, but we want to skip watch events
},
@@ -268,8 +270,8 @@ type bulkWroker struct {
}
// This will remove everything from the `resource` and `resource_history` table for a given namespace/group/resource
func (w *bulkWroker) deleteCollection(key *resource.ResourceKey) (*resource.BulkResponse_Summary, error) {
summary := &resource.BulkResponse_Summary{
func (w *bulkWroker) deleteCollection(key *resourcepb.ResourceKey) (*resourcepb.BulkResponse_Summary, error) {
summary := &resourcepb.BulkResponse_Summary{
Namespace: key.Namespace,
Group: key.Group,
Resource: key.Resource,
@@ -306,8 +308,8 @@ func (w *bulkWroker) deleteCollection(key *resource.ResourceKey) (*resource.Bulk
}
// Copy the latest value from history into the active resource table
func (w *bulkWroker) syncCollection(key *resource.ResourceKey, summary *resource.BulkResponse_Summary) error {
w.logger.Info("synchronize collection", "key", key.NSGR())
func (w *bulkWroker) syncCollection(key *resourcepb.ResourceKey, summary *resourcepb.BulkResponse_Summary) error {
w.logger.Info("synchronize collection", "key", resource.NSGR(key))
_, err := dbutil.Exec(w.ctx, w.tx, sqlResourceInsertFromHistory, &sqlResourceInsertFromHistoryRequest{
SQLTemplate: sqltemplate.New(w.dialect),
Key: key,
@@ -316,7 +318,7 @@ func (w *bulkWroker) syncCollection(key *resource.ResourceKey, summary *resource
return err
}
w.logger.Info("get stats (still in transaction)", "key", key.NSGR())
w.logger.Info("get stats (still in transaction)", "key", resource.NSGR(key))
rows, err := dbutil.QueryRows(w.ctx, w.tx, sqlResourceStats, &sqlStatsRequest{
SQLTemplate: sqltemplate.New(w.dialect),
Namespace: key.Namespace,
+3 -2
View File
@@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
@@ -197,13 +198,13 @@ func (p *pollingNotifier) poll(ctx context.Context, grp string, res string, sinc
}
stream <- &resource.WrittenEvent{
Value: rec.Value,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: rec.Key.Namespace,
Group: rec.Key.Group,
Resource: rec.Key.Resource,
Name: rec.Key.Name,
},
Type: resource.WatchEvent_Type(rec.Action),
Type: resourcepb.WatchEvent_Type(rec.Action),
PreviousRV: *prevRV,
Folder: rec.Folder,
ResourceVersion: rec.ResourceVersion,
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace/noop"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
@@ -209,7 +209,7 @@ func TestPollingNotifier(t *testing.T) {
defer close(done)
testEvent := &historyPollResponse{
Key: resource.ResourceKey{
Key: resourcepb.ResourceKey{
Namespace: "test-ns",
Group: "test-group",
Resource: "test-resource",
+5 -2
View File
@@ -6,7 +6,10 @@ import (
"time"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/stretchr/testify/require"
)
@@ -21,8 +24,8 @@ func TestChannelNotifier(t *testing.T) {
require.NoError(t, err)
testEvent := &resource.WrittenEvent{
Type: resource.WatchEvent_ADDED,
Key: &resource.ResourceKey{
Type: resourcepb.WatchEvent_ADDED,
Key: &resourcepb.ResourceKey{
Group: "test",
Resource: "test",
Name: "test1",
+18 -17
View File
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
@@ -88,7 +89,7 @@ func (r sqlResourceRequest) Validate() error {
type sqlResourceInsertFromHistoryRequest struct {
sqltemplate.SQLTemplate
Key *resource.ResourceKey
Key *resourcepb.ResourceKey
}
func (r sqlResourceInsertFromHistoryRequest) Validate() error {
@@ -115,7 +116,7 @@ func (r sqlStatsRequest) Validate() error {
}
type historyPollResponse struct {
Key resource.ResourceKey
Key resourcepb.ResourceKey
GUID string
ResourceVersion int64
PreviousRV *int64
@@ -148,7 +149,7 @@ func (r *sqlResourceHistoryPollRequest) Results() (*historyPollResponse, error)
prevRV = new(int64)
}
return &historyPollResponse{
Key: resource.ResourceKey{
Key: resourcepb.ResourceKey{
Namespace: r.Response.Key.Namespace,
Group: r.Response.Key.Group,
Resource: r.Response.Key.Resource,
@@ -165,13 +166,13 @@ func (r *sqlResourceHistoryPollRequest) Results() (*historyPollResponse, error)
// sqlResourceReadRequest can be used to retrieve a row fromthe "resource" tables.
func NewReadResponse() *resource.BackendReadResponse {
return &resource.BackendReadResponse{
Key: &resource.ResourceKey{},
Key: &resourcepb.ResourceKey{},
}
}
type sqlResourceReadRequest struct {
sqltemplate.SQLTemplate
Request *resource.ReadRequest
Request *resourcepb.ReadRequest
Response *resource.BackendReadResponse
}
@@ -186,7 +187,7 @@ func (r *sqlResourceReadRequest) Results() (*resource.BackendReadResponse, error
// List
type sqlResourceListRequest struct {
sqltemplate.SQLTemplate
Request *resource.ListRequest
Request *resourcepb.ListRequest
}
func (r sqlResourceListRequest) Validate() error {
@@ -194,7 +195,7 @@ func (r sqlResourceListRequest) Validate() error {
}
type historyReadRequest struct {
Key *resource.ResourceKey
Key *resourcepb.ResourceKey
ResourceVersion int64
}
@@ -213,8 +214,8 @@ func (r sqlResourceHistoryReadRequest) Results() (*resource.BackendReadResponse,
}
type historyReadLatestRVRequest struct {
Key *resource.ResourceKey
EventType resource.WatchEvent_Type
Key *resourcepb.ResourceKey
EventType resourcepb.WatchEvent_Type
}
type sqlResourceHistoryReadLatestRVRequest struct {
@@ -242,25 +243,25 @@ func (r *resourceHistoryReadLatestRVResponse) Results() (*resourceHistoryReadLat
type historyListRequest struct {
ResourceVersion, Limit, Offset int64
Folder string
Options *resource.ListOptions
Options *resourcepb.ListOptions
}
type sqlResourceHistoryListRequest struct {
sqltemplate.SQLTemplate
Request *historyListRequest
Response *resource.ResourceWrapper
Response *resourcepb.ResourceWrapper
}
func (r sqlResourceHistoryListRequest) Validate() error {
return nil // TODO
}
func (r sqlResourceHistoryListRequest) Results() (*resource.ResourceWrapper, error) {
func (r sqlResourceHistoryListRequest) Results() (*resourcepb.ResourceWrapper, error) {
// sqlResourceHistoryListRequest is a set-returning query. As such, it
// should not return its *Response, since that will be overwritten in the
// next call to `Scan`, so it needs to return a copy of it. Note, though,
// that it is safe to return the same `Response.Value` since `Scan`
// allocates a new slice of bytes each time.
return &resource.ResourceWrapper{
return &resourcepb.ResourceWrapper{
ResourceVersion: r.Response.ResourceVersion,
Value: r.Response.Value,
}, nil
@@ -292,7 +293,7 @@ func (r *sqlResourceHistoryDeleteRequest) Validate() error {
type sqlGetHistoryRequest struct {
sqltemplate.SQLTemplate
Key *resource.ResourceKey
Key *resourcepb.ResourceKey
Trash bool // only deleted items
StartRV int64 // from NextPageToken
MinRV int64 // minimum resource version for NotOlderThan
@@ -307,7 +308,7 @@ func (r sqlGetHistoryRequest) Validate() error {
// prune resource history
type sqlPruneHistoryRequest struct {
sqltemplate.SQLTemplate
Key *resource.ResourceKey
Key *resourcepb.ResourceKey
PartitionByGeneration bool // include generation in the partition
HistoryLimit int64
}
@@ -335,7 +336,7 @@ type sqlResourceBlobInsertRequest struct {
sqltemplate.SQLTemplate
Now time.Time
Info *utils.BlobInfo
Key *resource.ResourceKey
Key *resourcepb.ResourceKey
Value []byte
ContentType string
}
@@ -349,7 +350,7 @@ func (r sqlResourceBlobInsertRequest) Validate() error {
type sqlResourceBlobQueryRequest struct {
sqltemplate.SQLTemplate
Key *resource.ResourceKey
Key *resourcepb.ResourceKey
UID string
}
+27 -26
View File
@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks"
)
@@ -21,7 +22,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Data: &sqlResourceRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "nn",
Group: "gg",
Resource: "rr",
@@ -37,13 +38,13 @@ func TestUnifiedStorageQueries(t *testing.T) {
Data: &sqlResourceRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "nn",
Group: "gg",
Resource: "rr",
Name: "name",
},
Type: resource.WatchEvent_ADDED,
Type: resourcepb.WatchEvent_ADDED,
PreviousRV: 123,
},
Folder: "fldr",
@@ -56,7 +57,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Data: &sqlResourceRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "nn",
Group: "gg",
Resource: "rr",
@@ -72,8 +73,8 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "without_resource_version",
Data: &sqlResourceReadRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Request: &resource.ReadRequest{
Key: &resource.ResourceKey{
Request: &resourcepb.ReadRequest{
Key: &resourcepb.ResourceKey{
Namespace: "nn",
Group: "gg",
Resource: "rr",
@@ -90,10 +91,10 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "filter_on_namespace",
Data: &sqlResourceListRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Request: &resource.ListRequest{
Request: &resourcepb.ListRequest{
Limit: 10,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: "ns",
},
},
@@ -109,13 +110,13 @@ func TestUnifiedStorageQueries(t *testing.T) {
SQLTemplate: mocks.NewTestingSQLTemplate(),
Request: &historyListRequest{
Limit: 10,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: "ns",
},
},
},
Response: new(resource.ResourceWrapper),
Response: new(resourcepb.ResourceWrapper),
},
},
},
@@ -152,7 +153,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
SQLTemplate: mocks.NewTestingSQLTemplate(),
Request: &historyReadRequest{
ResourceVersion: 123,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "ns",
Group: "gp",
Resource: "rs",
@@ -170,7 +171,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Data: &sqlResourceHistoryReadLatestRVRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Request: &historyReadLatestRVRequest{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "ns",
Group: "gp",
Resource: "rs",
@@ -185,13 +186,13 @@ func TestUnifiedStorageQueries(t *testing.T) {
Data: &sqlResourceHistoryReadLatestRVRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Request: &historyReadLatestRVRequest{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "ns",
Group: "gp",
Resource: "rs",
Name: "nm",
},
EventType: resource.WatchEvent_DELETED,
EventType: resourcepb.WatchEvent_DELETED,
},
Response: new(resourceHistoryReadLatestRVResponse),
},
@@ -218,7 +219,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
SQLTemplate: mocks.NewTestingSQLTemplate(),
Generation: 789,
WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "nn",
Group: "gg",
Resource: "rr",
@@ -236,7 +237,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "read object history",
Data: &sqlGetHistoryRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "nn",
Group: "gg",
Resource: "rr",
@@ -248,7 +249,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "read trash",
Data: &sqlGetHistoryRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "nn",
Group: "gg",
Resource: "rr",
@@ -260,7 +261,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "read trash second page",
Data: &sqlGetHistoryRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "nn",
Group: "gg",
Resource: "rr",
@@ -276,7 +277,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "max-versions",
Data: &sqlPruneHistoryRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: "provisioning.grafana.app",
Resource: "repositories",
@@ -289,7 +290,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "collapse-generations",
Data: &sqlPruneHistoryRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: "provisioning.grafana.app",
Resource: "repositories",
@@ -386,7 +387,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "basic",
Data: &sqlResourceBlobInsertRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "x",
Group: "g",
Resource: "r",
@@ -409,7 +410,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "basic",
Data: &sqlResourceBlobQueryRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "x",
Group: "g",
Resource: "r",
@@ -422,7 +423,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "resource", // NOTE: this returns multiple values
Data: &sqlResourceBlobQueryRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "x",
Group: "g",
Resource: "r",
@@ -454,7 +455,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
Name: "update",
Data: &sqlResourceInsertFromHistoryRequest{
SQLTemplate: mocks.NewTestingSQLTemplate(),
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: "dashboard.grafana.app",
Resource: "dashboards",
+9 -8
View File
@@ -8,15 +8,16 @@ import (
"sync"
"time"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
"github.com/grafana/grafana/pkg/storage/unified/sql/dbutil"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
"github.com/grafana/grafana/pkg/storage/unified/sql/dbutil"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
var (
@@ -82,9 +83,9 @@ type writeOpResult struct {
// writeOp is a write operation that is executed with an incremented resource version
type writeOp struct {
key *resource.ResourceKey // The key of the resource
fn WriteEventFunc // The function to execute to write the event
done chan writeOpResult // A channel informing the operation is done
key *resourcepb.ResourceKey // The key of the resource
fn WriteEventFunc // The function to execute to write the event
done chan writeOpResult // A channel informing the operation is done
}
// WriteEventFunc is a function that writes a resource to the database
@@ -128,7 +129,7 @@ func NewResourceVersionManager(opts ResourceManagerOptions) (*resourceVersionMan
}
// ExecWithRV executes the given function with an incremented resource version
func (m *resourceVersionManager) ExecWithRV(ctx context.Context, key *resource.ResourceKey, fn WriteEventFunc) (rv int64, err error) {
func (m *resourceVersionManager) ExecWithRV(ctx context.Context, key *resourcepb.ResourceKey, fn WriteEventFunc) (rv int64, err error) {
rvmInflightWrites.WithLabelValues(key.Group, key.Resource).Inc()
defer rvmInflightWrites.WithLabelValues(key.Group, key.Resource).Dec()
+5 -4
View File
@@ -3,13 +3,14 @@ package sql
import (
"testing"
sqlmock "github.com/DATA-DOG/go-sqlmock"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
"github.com/grafana/grafana/pkg/storage/unified/sql/test"
"github.com/grafana/grafana/pkg/util/testutil"
"github.com/stretchr/testify/require"
)
func expectSuccessfulResourceVersionLock(t *testing.T, dbp test.TestDBProvider, rv int64, timestamp int64) {
@@ -44,7 +45,7 @@ func TestResourceVersionManager(t *testing.T) {
require.NotNil(t, manager)
t.Run("should handle single operation", func(t *testing.T) {
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: "test-group",
Resource: "test-resource",
}
+10 -9
View File
@@ -6,17 +6,18 @@ import (
"net/http"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
"github.com/grafana/grafana/pkg/storage/unified/sql/dbutil"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
// Support using SQL as fallback when the indexer is not running
var _ resource.ResourceIndexServer = &backend{}
var _ resourcepb.ResourceIndexServer = &backend{}
// GetStats implements resource.ResourceIndexServer.
// This will use the SQL index to count values
func (b *backend) GetStats(ctx context.Context, req *resource.ResourceStatsRequest) (*resource.ResourceStatsResponse, error) {
func (b *backend) GetStats(ctx context.Context, req *resourcepb.ResourceStatsRequest) (*resourcepb.ResourceStatsResponse, error) {
ctx, span := b.tracer.Start(ctx, tracePrefix+".GetStats")
defer span.End()
@@ -26,7 +27,7 @@ func (b *backend) GetStats(ctx context.Context, req *resource.ResourceStatsReque
Folder: req.Folder,
}
rsp := &resource.ResourceStatsResponse{}
rsp := &resourcepb.ResourceStatsResponse{}
err := b.db.WithTx(ctx, ReadCommittedRO, func(ctx context.Context, tx db.Tx) error {
rows, err := dbutil.QueryRows(ctx, tx, sqlResourceStats, sreq)
if err != nil {
@@ -39,7 +40,7 @@ func (b *backend) GetStats(ctx context.Context, req *resource.ResourceStatsReque
return err
}
rsp.Stats = append(rsp.Stats, &resource.ResourceStatsResponse_Stats{
rsp.Stats = append(rsp.Stats, &resourcepb.ResourceStatsResponse_Stats{
Group: row.Group,
Resource: row.Resource,
Count: row.Count,
@@ -53,18 +54,18 @@ func (b *backend) GetStats(ctx context.Context, req *resource.ResourceStatsReque
return rsp, nil
}
func (b *backend) RepositoryList(ctx context.Context, req *resource.ListManagedObjectsRequest) (*resource.ListManagedObjectsResponse, error) {
func (b *backend) RepositoryList(ctx context.Context, req *resourcepb.ListManagedObjectsRequest) (*resourcepb.ListManagedObjectsResponse, error) {
return nil, fmt.Errorf("SQL backend does not implement RepositoryList")
}
func (b *backend) RepositoryStats(context.Context, *resource.CountManagedObjectsRequest) (*resource.CountManagedObjectsResponse, error) {
func (b *backend) RepositoryStats(context.Context, *resourcepb.CountManagedObjectsRequest) (*resourcepb.CountManagedObjectsResponse, error) {
return nil, fmt.Errorf("SQL backend does not implement RepositoryStats")
}
// Search implements resource.ResourceIndexServer.
func (b *backend) Search(context.Context, *resource.ResourceSearchRequest) (*resource.ResourceSearchResponse, error) {
return &resource.ResourceSearchResponse{
Error: &resource.ErrorResult{
func (b *backend) Search(context.Context, *resourcepb.ResourceSearchRequest) (*resourcepb.ResourceSearchResponse, error) {
return &resourcepb.ResourceSearchResponse{
Error: &resourcepb.ErrorResult{
Code: http.StatusNotImplemented,
Message: "SQL backend does not implement Search",
},
+7 -6
View File
@@ -25,6 +25,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resource/grpc"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
)
@@ -136,12 +137,12 @@ func (s *service) start(ctx context.Context) error {
}
srv := s.handler.GetServer()
resource.RegisterResourceStoreServer(srv, server)
resource.RegisterBulkStoreServer(srv, server)
resource.RegisterResourceIndexServer(srv, server)
resource.RegisterManagedObjectIndexServer(srv, server)
resource.RegisterBlobStoreServer(srv, server)
resource.RegisterDiagnosticsServer(srv, server)
resourcepb.RegisterResourceStoreServer(srv, server)
resourcepb.RegisterBulkStoreServer(srv, server)
resourcepb.RegisterResourceIndexServer(srv, server)
resourcepb.RegisterManagedObjectIndexServer(srv, server)
resourcepb.RegisterBlobStoreServer(srv, server)
resourcepb.RegisterDiagnosticsServer(srv, server)
grpc_health_v1.RegisterHealthServer(srv, healthService)
// register reflection service
@@ -13,12 +13,14 @@ import (
"github.com/grafana/authlib/authn"
"github.com/grafana/authlib/types"
"github.com/grafana/dskit/services"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/sql"
"github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl"
@@ -157,7 +159,7 @@ func TestClientServer(t *testing.T) {
svc, err := sql.ProvideUnifiedStorageGrpcService(cfg, features, dbstore, nil, prometheus.NewPedanticRegistry(), nil, nil, nil, nil)
require.NoError(t, err)
var client resource.ResourceStoreClient
var client resourcepb.ResourceStoreClient
clientCtx := types.WithAuthInfo(context.Background(), authn.NewAccessTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{
Claims: jwt.Claims{
@@ -193,7 +195,7 @@ func TestClientServer(t *testing.T) {
},
"spec": {}
}`)
resp, err := client.Create(clientCtx, &resource.CreateRequest{
resp, err := client.Create(clientCtx, &resourcepb.CreateRequest{
Key: resourceKey("item1"),
Value: raw,
})
@@ -203,7 +205,7 @@ func TestClientServer(t *testing.T) {
})
t.Run("Read the resource", func(t *testing.T) {
resp, err := client.Read(clientCtx, &resource.ReadRequest{
resp, err := client.Read(clientCtx, &resourcepb.ReadRequest{
Key: resourceKey("item1"),
})
require.NoError(t, err)
@@ -217,8 +219,8 @@ func TestClientServer(t *testing.T) {
})
}
func resourceKey(name string) *resource.ResourceKey {
return &resource.ResourceKey{
func resourceKey(name string) *resourcepb.ResourceKey {
return &resourcepb.ResourceKey{
Namespace: "namespace",
Group: "group",
Resource: "resource",
+7 -5
View File
@@ -10,6 +10,8 @@ import (
"time"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -53,7 +55,7 @@ func initializeBackend(ctx context.Context, backend resource.StorageBackend, opt
group := fmt.Sprintf("group-%d", g)
for r := 0; r < opts.NumResourceTypes; r++ {
resourceType := fmt.Sprintf("resource-%d", r)
_, err := writeEvent(ctx, backend, "init", resource.WatchEvent_ADDED,
_, err := writeEvent(ctx, backend, "init", resourcepb.WatchEvent_ADDED,
WithNamespace(namespace),
WithGroup(group),
WithResource(resourceType),
@@ -104,7 +106,7 @@ func runStorageBackendBenchmark(ctx context.Context, backend resource.StorageBac
name := fmt.Sprintf("item-%d", uniqueID)
writeStart := time.Now()
_, err := writeEvent(ctx, backend, name, resource.WatchEvent_ADDED,
_, err := writeEvent(ctx, backend, name, resourcepb.WatchEvent_ADDED,
WithNamespace(namespace),
WithGroup(group),
WithResource(resourceType),
@@ -232,7 +234,7 @@ func runSearchBackendBenchmarkWriteThroughput(ctx context.Context, backend resou
for jobID := range jobs {
doc := &resource.IndexableDocument{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: nr.Namespace,
Group: nr.Group,
Resource: nr.Resource,
@@ -420,7 +422,7 @@ func BenchmarkIndexServer(tb testing.TB, ctx context.Context, backend resource.S
// testDocumentBuilder implements DocumentBuilder for testing
type testDocumentBuilder struct{}
func (b *testDocumentBuilder) BuildDocument(ctx context.Context, key *resource.ResourceKey, rv int64, value []byte) (*resource.IndexableDocument, error) {
func (b *testDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*resource.IndexableDocument, error) {
// convert value to unstructured.Unstructured
var u unstructured.Unstructured
if err := u.UnmarshalJSON(value); err != nil {
@@ -451,7 +453,7 @@ func (b *testDocumentBuilder) BuildDocument(ctx context.Context, key *resource.R
}
}
return &resource.IndexableDocument{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: key.Namespace,
Group: key.Group,
Resource: key.Resource,
@@ -7,9 +7,12 @@ import (
"github.com/stretchr/testify/require"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@@ -48,7 +51,7 @@ func RunTestSearchAndStorage(t *testing.T, ctx context.Context, backend resource
}
for _, doc := range initialResources {
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: "test.grafana.app",
Resource: "testresources",
Namespace: nsPrefix,
@@ -80,7 +83,7 @@ func RunTestSearchAndStorage(t *testing.T, ctx context.Context, backend resource
// Create document
rv, err := backend.WriteEvent(ctx, resource.WriteEvent{
Type: resource.WatchEvent_ADDED,
Type: resourcepb.WatchEvent_ADDED,
Key: key,
Value: value,
Object: meta,
@@ -111,9 +114,9 @@ func RunTestSearchAndStorage(t *testing.T, ctx context.Context, backend resource
t.Run("Search for initial resources", func(t *testing.T) {
// Test 1: Search for initial resources
searchResp, err := server.Search(ctx, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
searchResp, err := server.Search(ctx, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test.grafana.app",
Resource: "testresources",
Namespace: nsPrefix,
@@ -152,7 +155,7 @@ func RunTestSearchAndStorage(t *testing.T, ctx context.Context, backend resource
}
for _, doc := range testDocs {
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: "test.grafana.app",
Resource: "testresources",
Namespace: nsPrefix,
@@ -180,7 +183,7 @@ func RunTestSearchAndStorage(t *testing.T, ctx context.Context, backend resource
require.NoError(t, err)
// Create document
createResp, err := server.Create(ctx, &resource.CreateRequest{
createResp, err := server.Create(ctx, &resourcepb.CreateRequest{
Key: key,
Value: value,
})
@@ -194,9 +197,9 @@ func RunTestSearchAndStorage(t *testing.T, ctx context.Context, backend resource
})
t.Run("Search for documents", func(t *testing.T) {
searchResp, err := server.Search(ctx, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
searchResp, err := server.Search(ctx, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test.grafana.app",
Resource: "testresources",
Namespace: nsPrefix,
@@ -212,9 +215,9 @@ func RunTestSearchAndStorage(t *testing.T, ctx context.Context, backend resource
})
t.Run("Search with tags", func(t *testing.T) {
searchResp, err := server.Search(ctx, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
searchResp, err := server.Search(ctx, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test.grafana.app",
Resource: "testresources",
Namespace: nsPrefix,
@@ -230,9 +233,9 @@ func RunTestSearchAndStorage(t *testing.T, ctx context.Context, backend resource
})
t.Run("Search with specific tag", func(t *testing.T) {
searchResp, err := server.Search(ctx, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
searchResp, err := server.Search(ctx, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "test.grafana.app",
Resource: "testresources",
Namespace: nsPrefix,
+14 -13
View File
@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/util/testutil"
)
@@ -74,7 +75,7 @@ func runTestSearchBackendBuildIndex(t *testing.T, backend resource.SearchBackend
{
Action: resource.ActionIndex,
Doc: &resource.IndexableDocument{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: ns.Namespace,
Group: ns.Group,
Resource: ns.Resource,
@@ -120,7 +121,7 @@ func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix
{
Action: resource.ActionIndex,
Doc: &resource.IndexableDocument{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: ns.Namespace,
Group: ns.Group,
Resource: ns.Resource,
@@ -137,7 +138,7 @@ func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix
{
Action: resource.ActionIndex,
Doc: &resource.IndexableDocument{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: ns.Namespace,
Group: ns.Group,
Resource: ns.Resource,
@@ -160,9 +161,9 @@ func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix
require.NotNil(t, index)
t.Run("Search", func(t *testing.T) {
resp, err := index.Search(ctx, nil, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
resp, err := index.Search(ctx, nil, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns.Namespace,
Group: ns.Group,
Resource: ns.Resource,
@@ -177,9 +178,9 @@ func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix
require.Equal(t, int64(1), resp.TotalHits) // Only doc3 should have tag3 now
// Search for Document
resp, err = index.Search(ctx, nil, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
resp, err = index.Search(ctx, nil, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns.Namespace,
Group: ns.Group,
Resource: ns.Resource,
@@ -201,7 +202,7 @@ func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix
{
Action: resource.ActionIndex,
Doc: &resource.IndexableDocument{
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: ns.Namespace,
Group: ns.Group,
Resource: ns.Resource,
@@ -219,9 +220,9 @@ func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix
})
require.NoError(t, err)
// Search for Document
resp, err := index.Search(ctx, nil, &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
resp, err := index.Search(ctx, nil, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns.Namespace,
Group: ns.Group,
Resource: ns.Resource,
+7 -4
View File
@@ -7,9 +7,12 @@ import (
"testing"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/stretchr/testify/require"
)
@@ -137,7 +140,7 @@ func runTestResourcePermissionScenarios(t *testing.T, backend resource.StorageBa
ctx := types.WithAuthInfo(context.Background(), testUser)
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Group: "test.grafana.app",
Resource: "testresources",
Namespace: nsPrefix + "-ns1",
@@ -163,7 +166,7 @@ func runTestResourcePermissionScenarios(t *testing.T, backend resource.StorageBa
}`, resourceName, resourceUID, nsPrefix+"-ns1", tc.initialFolder, i)
checksPerformed = []types.CheckRequest{}
created, err := server.Create(ctx, &resource.CreateRequest{
created, err := server.Create(ctx, &resourcepb.CreateRequest{
Value: []byte(resourceJSON),
Key: key,
})
@@ -202,7 +205,7 @@ func runTestResourcePermissionScenarios(t *testing.T, backend resource.StorageBa
// Override permissions for initial creation to always succeed
mockAccess.allowed = true
created, err := server.Create(ctx, &resource.CreateRequest{
created, err := server.Create(ctx, &resourcepb.CreateRequest{
Value: []byte(initialResourceJSON),
Key: key,
})
@@ -229,7 +232,7 @@ func runTestResourcePermissionScenarios(t *testing.T, backend resource.StorageBa
mockAccess.allowed = false // Reset to use the map
checksPerformed = []types.CheckRequest{}
updated, err := server.Update(ctx, &resource.UpdateRequest{
updated, err := server.Update(ctx, &resourcepb.UpdateRequest{
Key: key,
Value: []byte(targetResourceJSON),
ResourceVersion: created.ResourceVersion,
+121 -121
View File
@@ -22,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/util/testutil"
)
@@ -108,34 +109,34 @@ func runTestIntegrationBackendHappyPath(t *testing.T, backend resource.StorageBa
var rv1, rv2, rv3, rv4, rv5 int64
t.Run("Add 3 resources", func(t *testing.T) {
rv1, err = writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED, WithNamespace(ns))
rv1, err = writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv1, int64(0))
rv2, err = writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED, WithNamespace(ns))
rv2, err = writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv2, rv1)
rv3, err = writeEvent(ctx, backend, "item3", resource.WatchEvent_ADDED, WithNamespace(ns))
rv3, err = writeEvent(ctx, backend, "item3", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv3, rv2)
})
t.Run("Update item2", func(t *testing.T) {
rv4, err = writeEvent(ctx, backend, "item2", resource.WatchEvent_MODIFIED, WithNamespace(ns))
rv4, err = writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv4, rv3)
})
t.Run("Delete item1", func(t *testing.T) {
rv5, err = writeEvent(ctx, backend, "item1", resource.WatchEvent_DELETED, WithNamespace(ns))
rv5, err = writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_DELETED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv5, rv4)
})
t.Run("Read latest item 2", func(t *testing.T) {
resp := backend.ReadResource(ctx, &resource.ReadRequest{
Key: &resource.ResourceKey{
resp := backend.ReadResource(ctx, &resourcepb.ReadRequest{
Key: &resourcepb.ResourceKey{
Name: "item2",
Namespace: ns,
Group: "group",
@@ -149,8 +150,8 @@ func runTestIntegrationBackendHappyPath(t *testing.T, backend resource.StorageBa
})
t.Run("Read early version of item2", func(t *testing.T) {
resp := backend.ReadResource(ctx, &resource.ReadRequest{
Key: &resource.ResourceKey{
resp := backend.ReadResource(ctx, &resourcepb.ReadRequest{
Key: &resourcepb.ResourceKey{
Name: "item2",
Namespace: ns,
Group: "group",
@@ -164,9 +165,9 @@ func runTestIntegrationBackendHappyPath(t *testing.T, backend resource.StorageBa
})
t.Run("List latest", func(t *testing.T) {
resp, err := server.List(ctx, &resource.ListRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
resp, err := server.List(ctx, &resourcepb.ListRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns,
Group: "group",
Resource: "resource",
@@ -185,29 +186,29 @@ func runTestIntegrationBackendHappyPath(t *testing.T, backend resource.StorageBa
event := <-stream
require.Equal(t, "item1", event.Key.Name)
require.Equal(t, rv1, event.ResourceVersion)
require.Equal(t, resource.WatchEvent_ADDED, event.Type)
require.Equal(t, resourcepb.WatchEvent_ADDED, event.Type)
require.Equal(t, "folderuid", event.Folder)
event = <-stream
require.Equal(t, "item2", event.Key.Name)
require.Equal(t, rv2, event.ResourceVersion)
require.Equal(t, resource.WatchEvent_ADDED, event.Type)
require.Equal(t, resourcepb.WatchEvent_ADDED, event.Type)
require.Equal(t, "folderuid", event.Folder)
event = <-stream
require.Equal(t, "item3", event.Key.Name)
require.Equal(t, rv3, event.ResourceVersion)
require.Equal(t, resource.WatchEvent_ADDED, event.Type)
require.Equal(t, resourcepb.WatchEvent_ADDED, event.Type)
event = <-stream
require.Equal(t, "item2", event.Key.Name)
require.Equal(t, rv4, event.ResourceVersion)
require.Equal(t, resource.WatchEvent_MODIFIED, event.Type)
require.Equal(t, resourcepb.WatchEvent_MODIFIED, event.Type)
event = <-stream
require.Equal(t, "item1", event.Key.Name)
require.Equal(t, rv5, event.ResourceVersion)
require.Equal(t, resource.WatchEvent_DELETED, event.Type)
require.Equal(t, resourcepb.WatchEvent_DELETED, event.Type)
})
}
@@ -224,31 +225,31 @@ func runTestIntegrationBackendGetResourceStats(t *testing.T, backend resource.St
return strings.Compare(a.Resource, b.Resource)
}
// Create resources across different namespaces/groups
_, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED,
_, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED,
WithNamespace(nsPrefix+"-ns1"),
WithGroup("group"),
WithResource("resource1"))
require.NoError(t, err)
_, err = writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED,
_, err = writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED,
WithNamespace(nsPrefix+"-ns1"),
WithGroup("group"),
WithResource("resource1"))
require.NoError(t, err)
_, err = writeEvent(ctx, backend, "item3", resource.WatchEvent_ADDED,
_, err = writeEvent(ctx, backend, "item3", resourcepb.WatchEvent_ADDED,
WithNamespace(nsPrefix+"-ns1"),
WithGroup("group"),
WithResource("resource2"))
require.NoError(t, err)
_, err = writeEvent(ctx, backend, "item4", resource.WatchEvent_ADDED,
_, err = writeEvent(ctx, backend, "item4", resourcepb.WatchEvent_ADDED,
WithNamespace(nsPrefix+"-ns2"),
WithGroup("group"),
WithResource("resource1"))
require.NoError(t, err)
_, err = writeEvent(ctx, backend, "item5", resource.WatchEvent_ADDED,
_, err = writeEvent(ctx, backend, "item5", resourcepb.WatchEvent_ADDED,
WithNamespace(nsPrefix+"-ns2"),
WithGroup("group"),
WithResource("resource1"))
@@ -311,7 +312,7 @@ func runTestIntegrationBackendWatchWriteEvents(t *testing.T, backend resource.St
ctx := testutil.NewTestContext(t, time.Now().Add(5*time.Second))
// Create a few resources before initing the watch
_, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED, WithNamespace(nsPrefix+"-ns1"))
_, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(nsPrefix+"-ns1"))
require.NoError(t, err)
// Start the watch
@@ -319,7 +320,7 @@ func runTestIntegrationBackendWatchWriteEvents(t *testing.T, backend resource.St
require.NoError(t, err)
// Create one more event
_, err = writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED, WithNamespace(nsPrefix+"-ns1"))
_, err = writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace(nsPrefix+"-ns1"))
require.NoError(t, err)
require.Equal(t, "item2", (<-stream).Key.Name)
@@ -335,35 +336,35 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend
server := newServer(t, backend)
ns := nsPrefix + "-ns1"
// Create a few resources before starting the watch
rv1, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED, WithNamespace(ns))
rv1, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv1, int64(0))
rv2, err := writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED, WithNamespace(ns))
rv2, err := writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv2, rv1)
rv3, err := writeEvent(ctx, backend, "item3", resource.WatchEvent_ADDED, WithNamespace(ns))
rv3, err := writeEvent(ctx, backend, "item3", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv3, rv2)
rv4, err := writeEvent(ctx, backend, "item4", resource.WatchEvent_ADDED, WithNamespace(ns))
rv4, err := writeEvent(ctx, backend, "item4", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv4, rv3)
rv5, err := writeEvent(ctx, backend, "item5", resource.WatchEvent_ADDED, WithNamespace(ns))
rv5, err := writeEvent(ctx, backend, "item5", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv5, rv4)
rv6, err := writeEvent(ctx, backend, "item2", resource.WatchEvent_MODIFIED, WithNamespace(ns))
rv6, err := writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv6, rv5)
rv7, err := writeEvent(ctx, backend, "item3", resource.WatchEvent_DELETED, WithNamespace(ns))
rv7, err := writeEvent(ctx, backend, "item3", resourcepb.WatchEvent_DELETED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv7, rv6)
rv8, err := writeEvent(ctx, backend, "item6", resource.WatchEvent_ADDED, WithNamespace(ns))
rv8, err := writeEvent(ctx, backend, "item6", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv8, rv7)
t.Run("fetch all latest", func(t *testing.T) {
res, err := server.List(ctx, &resource.ListRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
res, err := server.List(ctx, &resourcepb.ListRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "group",
Resource: "resource",
},
@@ -383,10 +384,10 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend
})
t.Run("list latest first page ", func(t *testing.T) {
res, err := server.List(ctx, &resource.ListRequest{
res, err := server.List(ctx, &resourcepb.ListRequest{
Limit: 3,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "group",
Resource: "resource",
},
@@ -404,10 +405,10 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend
})
t.Run("list at revision", func(t *testing.T) {
res, err := server.List(ctx, &resource.ListRequest{
res, err := server.List(ctx, &resourcepb.ListRequest{
ResourceVersion: rv4,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "group",
Resource: "resource",
},
@@ -424,11 +425,11 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend
})
t.Run("fetch first page at revision with limit", func(t *testing.T) {
res, err := server.List(ctx, &resource.ListRequest{
res, err := server.List(ctx, &resourcepb.ListRequest{
Limit: 3,
ResourceVersion: rv7,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "group",
Resource: "resource",
},
@@ -454,11 +455,11 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend
StartOffset: 2,
SortAscending: false,
}
res, err := server.List(ctx, &resource.ListRequest{
res, err := server.List(ctx, &resourcepb.ListRequest{
NextPageToken: continueToken.String(),
Limit: 2,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "group",
Resource: "resource",
},
@@ -482,28 +483,28 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second))
server := newServer(t, backend)
ns := nsPrefix + "-ns1"
rv1, _ := writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED, WithNamespace(ns))
rv1, _ := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.Greater(t, rv1, int64(0))
// add 5 events for item1 - should be saved to history
rvHistory1, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_MODIFIED, WithNamespace(ns))
rvHistory1, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvHistory1, rv1)
rvHistory2, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_MODIFIED, WithNamespace(ns))
rvHistory2, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvHistory2, rvHistory1)
rvHistory3, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_MODIFIED, WithNamespace(ns))
rvHistory3, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvHistory3, rvHistory2)
rvHistory4, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_MODIFIED, WithNamespace(ns))
rvHistory4, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvHistory4, rvHistory3)
rvHistory5, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_MODIFIED, WithNamespace(ns))
rvHistory5, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvHistory5, rvHistory4)
t.Run("fetch history with different version matching", func(t *testing.T) {
baseKey := &resource.ResourceKey{
baseKey := &resourcepb.ResourceKey{
Namespace: ns,
Group: "group",
Resource: "resource",
@@ -512,7 +513,7 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
tests := []struct {
name string
request *resource.ListRequest
request *resourcepb.ListRequest
expectedVersions []int64
expectedValues []string
minExpectedHeadRV int64
@@ -521,12 +522,12 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
}{
{
name: "NotOlderThan with rv1 (ASC order)",
request: &resource.ListRequest{
request: &resourcepb.ListRequest{
Limit: 3,
Source: resource.ListRequest_HISTORY,
Source: resourcepb.ListRequest_HISTORY,
ResourceVersion: rv1,
VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan,
Options: &resource.ListOptions{
VersionMatchV2: resourcepb.ResourceVersionMatchV2_NotOlderThan,
Options: &resourcepb.ListOptions{
Key: baseKey,
},
},
@@ -538,12 +539,12 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
},
{
name: "NotOlderThan with rv=0 (ASC order)",
request: &resource.ListRequest{
request: &resourcepb.ListRequest{
Limit: 3,
Source: resource.ListRequest_HISTORY,
Source: resourcepb.ListRequest_HISTORY,
ResourceVersion: 0,
VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan,
Options: &resource.ListOptions{
VersionMatchV2: resourcepb.ResourceVersionMatchV2_NotOlderThan,
Options: &resourcepb.ListOptions{
Key: baseKey,
},
},
@@ -555,10 +556,10 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
},
{
name: "ResourceVersionMatch_Unset (DESC order)",
request: &resource.ListRequest{
request: &resourcepb.ListRequest{
Limit: 3,
Source: resource.ListRequest_HISTORY,
Options: &resource.ListOptions{
Source: resourcepb.ListRequest_HISTORY,
Options: &resourcepb.ListOptions{
Key: baseKey,
},
},
@@ -597,12 +598,12 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
// Test pagination for NotOlderThan (second page)
t.Run("second page with NotOlderThan", func(t *testing.T) {
// Get first page
firstRequest := &resource.ListRequest{
firstRequest := &resourcepb.ListRequest{
Limit: 3,
Source: resource.ListRequest_HISTORY,
Source: resourcepb.ListRequest_HISTORY,
ResourceVersion: rv1,
VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan,
Options: &resource.ListOptions{Key: baseKey},
VersionMatchV2: resourcepb.ResourceVersionMatchV2_NotOlderThan,
Options: &resourcepb.ListOptions{Key: baseKey},
}
firstPageRes, err := server.List(ctx, firstRequest)
require.NoError(t, err)
@@ -612,13 +613,13 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
require.NoError(t, err)
// Get second page
secondPageRes, err := server.List(ctx, &resource.ListRequest{
secondPageRes, err := server.List(ctx, &resourcepb.ListRequest{
Limit: 3,
Source: resource.ListRequest_HISTORY,
Source: resourcepb.ListRequest_HISTORY,
ResourceVersion: rv1,
VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan,
VersionMatchV2: resourcepb.ResourceVersionMatchV2_NotOlderThan,
NextPageToken: continueToken.String(),
Options: &resource.ListOptions{Key: baseKey},
Options: &resourcepb.ListOptions{Key: baseKey},
})
require.NoError(t, err)
require.Nil(t, secondPageRes.Error)
@@ -639,12 +640,12 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
StartOffset: 2,
SortAscending: false,
}
res, err := server.List(ctx, &resource.ListRequest{
res, err := server.List(ctx, &resourcepb.ListRequest{
NextPageToken: continueToken.String(),
Limit: 2,
Source: resource.ListRequest_HISTORY,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Source: resourcepb.ListRequest_HISTORY,
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns,
Group: "group",
Resource: "resource",
@@ -665,7 +666,7 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
t.Run("paginated history with NotOlderThan returns items in ascending order", func(t *testing.T) {
// Create 10 versions of a resource to test pagination
ns2 := nsPrefix + "-ns2"
resourceKey := &resource.ResourceKey{
resourceKey := &resourcepb.ResourceKey{
Namespace: ns2,
Group: "group",
Resource: "resource",
@@ -675,13 +676,13 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
var resourceVersions []int64
// First create the initial resource
initialRV, err := writeEvent(ctx, backend, "paged-item", resource.WatchEvent_ADDED, WithNamespace(ns2))
initialRV, err := writeEvent(ctx, backend, "paged-item", resourcepb.WatchEvent_ADDED, WithNamespace(ns2))
require.NoError(t, err)
resourceVersions = append(resourceVersions, initialRV)
// Create 9 more versions with modifications
for i := 0; i < 9; i++ {
rv, err := writeEvent(ctx, backend, "paged-item", resource.WatchEvent_MODIFIED, WithNamespace(ns2))
rv, err := writeEvent(ctx, backend, "paged-item", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns2))
require.NoError(t, err)
resourceVersions = append(resourceVersions, rv)
}
@@ -701,16 +702,16 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
{pageNumber: 4, pageSize: 1, startToken: ""}, // Will be set in the test - last page with remaining item
}
var allItems []*resource.ResourceWrapper
var allItems []*resourcepb.ResourceWrapper
// Request first page with NotOlderThan and ResourceVersion=0 (should start from oldest)
for i, page := range pages {
req := &resource.ListRequest{
req := &resourcepb.ListRequest{
Limit: int64(page.pageSize),
Source: resource.ListRequest_HISTORY,
Source: resourcepb.ListRequest_HISTORY,
ResourceVersion: 0,
VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan,
Options: &resource.ListOptions{
VersionMatchV2: resourcepb.ResourceVersionMatchV2_NotOlderThan,
Options: &resourcepb.ListOptions{
Key: resourceKey,
},
}
@@ -781,17 +782,17 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
t.Run("fetch history with deleted item", func(t *testing.T) {
// Create a resource and delete it
rv, err := writeEvent(ctx, backend, "deleted-item", resource.WatchEvent_ADDED, WithNamespace(ns))
rv, err := writeEvent(ctx, backend, "deleted-item", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
rvDeleted, err := writeEvent(ctx, backend, "deleted-item", resource.WatchEvent_DELETED, WithNamespace(ns))
rvDeleted, err := writeEvent(ctx, backend, "deleted-item", resourcepb.WatchEvent_DELETED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvDeleted, rv)
// Fetch history for the deleted item
res, err := server.List(ctx, &resource.ListRequest{
Source: resource.ListRequest_HISTORY,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
res, err := server.List(ctx, &resourcepb.ListRequest{
Source: resourcepb.ListRequest_HISTORY,
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns,
Group: "group",
Resource: "resource",
@@ -806,25 +807,25 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage
t.Run("fetch history with recreated item", func(t *testing.T) {
// Create a resource and delete it
rv, err := writeEvent(ctx, backend, "deleted-item", resource.WatchEvent_ADDED, WithNamespace(ns))
rv, err := writeEvent(ctx, backend, "deleted-item", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
rvDeleted, err := writeEvent(ctx, backend, "deleted-item", resource.WatchEvent_DELETED, WithNamespace(ns))
rvDeleted, err := writeEvent(ctx, backend, "deleted-item", resourcepb.WatchEvent_DELETED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvDeleted, rv)
// Create a few more versions after deletion
rv1, err := writeEvent(ctx, backend, "deleted-item", resource.WatchEvent_ADDED, WithNamespace(ns))
rv1, err := writeEvent(ctx, backend, "deleted-item", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv1, rvDeleted)
rv2, err := writeEvent(ctx, backend, "deleted-item", resource.WatchEvent_MODIFIED, WithNamespace(ns))
rv2, err := writeEvent(ctx, backend, "deleted-item", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv2, rv1)
// Fetch history for the deleted item
res, err := server.List(ctx, &resource.ListRequest{
Source: resource.ListRequest_HISTORY,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
res, err := server.List(ctx, &resourcepb.ListRequest{
Source: resourcepb.ListRequest_HISTORY,
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns,
Group: "group",
Resource: "resource",
@@ -854,26 +855,26 @@ func runTestIntegrationBackendListHistoryErrorReporting(t *testing.T, backend re
)
start := time.Now()
origRv, _ := writeEvent(ctx, backend, name, resource.WatchEvent_ADDED, WithNamespace(ns), WithGroup(group), WithResource(resourceName))
origRv, _ := writeEvent(ctx, backend, name, resourcepb.WatchEvent_ADDED, WithNamespace(ns), WithGroup(group), WithResource(resourceName))
require.Greater(t, origRv, int64(0))
const events = 500
prevRv := origRv
for i := 0; i < events; i++ {
rv, err := writeEvent(ctx, backend, name, resource.WatchEvent_MODIFIED, WithNamespace(ns), WithGroup(group), WithResource(resourceName))
rv, err := writeEvent(ctx, backend, name, resourcepb.WatchEvent_MODIFIED, WithNamespace(ns), WithGroup(group), WithResource(resourceName))
require.NoError(t, err)
require.Greater(t, rv, prevRv)
prevRv = rv
}
t.Log("added events in ", time.Since(start))
req := &resource.ListRequest{
req := &resourcepb.ListRequest{
Limit: 2 * events,
Source: resource.ListRequest_HISTORY,
Source: resourcepb.ListRequest_HISTORY,
ResourceVersion: origRv,
VersionMatchV2: resource.ResourceVersionMatchV2_NotOlderThan,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
VersionMatchV2: resourcepb.ResourceVersionMatchV2_NotOlderThan,
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns,
Group: group,
Resource: resourceName,
@@ -903,16 +904,15 @@ func runTestIntegrationBlobSupport(t *testing.T, backend resource.StorageBackend
ns := nsPrefix + "-ns1"
t.Run("put and fetch blob", func(t *testing.T) {
key := &resource.ResourceKey{
key := &resourcepb.ResourceKey{
Namespace: ns,
Group: "g",
Resource: "r",
Name: "n",
}
b1, err := server.PutBlob(ctx, &resource.PutBlobRequest{
b1, err := server.PutBlob(ctx, &resourcepb.PutBlobRequest{
Resource: key,
Method: resource.PutBlobRequest_GRPC,
Method: resourcepb.PutBlobRequest_GRPC,
ContentType: "plain/text",
Value: []byte("hello 11111"),
})
@@ -920,9 +920,9 @@ func runTestIntegrationBlobSupport(t *testing.T, backend resource.StorageBackend
require.Nil(t, b1.Error)
require.Equal(t, "c894ae57bd227b8f8c63f38a2ddf458b", b1.Hash)
b2, err := server.PutBlob(ctx, &resource.PutBlobRequest{
b2, err := server.PutBlob(ctx, &resourcepb.PutBlobRequest{
Resource: key,
Method: resource.PutBlobRequest_GRPC,
Method: resourcepb.PutBlobRequest_GRPC,
ContentType: "plain/text",
Value: []byte("hello 22222"), // the most recent
})
@@ -950,19 +950,19 @@ func runTestIntegrationBlobSupport(t *testing.T, backend resource.StorageBackend
obj.SetKind("Test")
val, err := obj.MarshalJSON()
require.NoError(t, err)
out, err := server.Create(ctx, &resource.CreateRequest{Key: key, Value: val})
out, err := server.Create(ctx, &resourcepb.CreateRequest{Key: key, Value: val})
require.NoError(t, err)
require.Nil(t, out.Error)
require.True(t, out.ResourceVersion > 0)
// The server (not store!) will lookup the saved annotation and return the correct payload
res, err := server.GetBlob(ctx, &resource.GetBlobRequest{Resource: key})
res, err := server.GetBlob(ctx, &resourcepb.GetBlobRequest{Resource: key})
require.NoError(t, err)
require.Nil(t, out.Error)
require.Contains(t, string(res.Value), "hello 22222")
// But we can still get an older version with an explicit UID
res, err = server.GetBlob(ctx, &resource.GetBlobRequest{Resource: key, Uid: b1.Uid})
res, err = server.GetBlob(ctx, &resourcepb.GetBlobRequest{Resource: key, Uid: b1.Uid})
require.NoError(t, err)
require.Nil(t, out.Error)
require.Contains(t, string(res.Value), "hello 11111")
@@ -981,8 +981,8 @@ func runTestIntegrationBackendCreateNewResource(t *testing.T, backend resource.S
ns := nsPrefix + "-create-resource"
ctx = request.WithNamespace(ctx, ns)
request := &resource.CreateRequest{
Key: &resource.ResourceKey{
request := &resourcepb.CreateRequest{
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: "test.grafana",
Resource: "Test",
@@ -1064,7 +1064,7 @@ type WriteEventOptions struct {
Value []byte
}
func writeEvent(ctx context.Context, store resource.StorageBackend, name string, action resource.WatchEvent_Type, opts ...WriteEventOption) (int64, error) {
func writeEvent(ctx context.Context, store resource.StorageBackend, name string, action resourcepb.WatchEvent_Type, opts ...WriteEventOption) (int64, error) {
// Default options
options := WriteEventOptions{
Namespace: "namespace",
@@ -1087,7 +1087,7 @@ func writeEvent(ctx context.Context, store resource.StorageBackend, name string,
"namespace": options.Namespace,
},
"spec": map[string]any{
"value": name + " " + resource.WatchEvent_Type_name[int32(action)],
"value": name + " " + resourcepb.WatchEvent_Type_name[int32(action)],
},
},
}
@@ -1106,7 +1106,7 @@ func writeEvent(ctx context.Context, store resource.StorageBackend, name string,
return store.WriteEvent(ctx, resource.WriteEvent{
Type: action,
Value: options.Value,
Key: &resource.ResourceKey{
Key: &resourcepb.ResourceKey{
Namespace: options.Namespace,
Group: options.Group,
Resource: options.Resource,