Search: Replace Origin calls with Repository (#98754)

This commit is contained in:
Ryan McKinley 2025-01-10 21:27:10 +03:00 committed by GitHub
parent 3eace5f7c8
commit ed39259461
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1211 additions and 652 deletions

View File

@ -57,9 +57,12 @@ func (d *directResourceClient) List(ctx context.Context, in *resource.ListReques
return d.server.List(ctx, in) return d.server.List(ctx, in)
} }
// Origin implements ResourceClient. func (d *directResourceClient) ListRepositoryObjects(ctx context.Context, in *resource.ListRepositoryObjectsRequest, opts ...grpc.CallOption) (*resource.ListRepositoryObjectsResponse, error) {
func (d *directResourceClient) Origin(ctx context.Context, in *resource.OriginRequest, opts ...grpc.CallOption) (*resource.OriginResponse, error) { return d.server.ListRepositoryObjects(ctx, in)
return d.server.Origin(ctx, in) }
func (d *directResourceClient) CountRepositoryObjects(ctx context.Context, in *resource.CountRepositoryObjectsRequest, opts ...grpc.CallOption) (*resource.CountRepositoryObjectsResponse, error) {
return d.server.CountRepositoryObjects(ctx, in)
} }
// PutBlob implements ResourceClient. // PutBlob implements ResourceClient.

View File

@ -330,9 +330,12 @@ func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryR
return list, err return list, err
} }
// Used for efficient provisioning func (a *dashboardSqlAccess) ListRepositoryObjects(ctx context.Context, req *resource.ListRepositoryObjectsRequest) (*resource.ListRepositoryObjectsResponse, error) {
func (a *dashboardSqlAccess) Origin(context.Context, *resource.OriginRequest) (*resource.OriginResponse, error) { return nil, fmt.Errorf("not implemented")
return nil, fmt.Errorf("not yet (origin)") }
func (a *dashboardSqlAccess) CountRepositoryObjects(context.Context, *resource.CountRepositoryObjectsRequest) (*resource.CountRepositoryObjectsResponse, error) {
return nil, fmt.Errorf("not implemented")
} }
// GetStats implements ResourceServer. // GetStats implements ResourceServer.

View File

@ -8,11 +8,12 @@ import (
"github.com/fullstorydev/grpchan" "github.com/fullstorydev/grpchan"
"github.com/fullstorydev/grpchan/inprocgrpc" "github.com/fullstorydev/grpchan/inprocgrpc"
authnlib "github.com/grafana/authlib/authn"
"github.com/grafana/authlib/claims"
grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"google.golang.org/grpc" "google.golang.org/grpc"
authnlib "github.com/grafana/authlib/authn"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn/grpcutils" "github.com/grafana/grafana/pkg/services/authn/grpcutils"
grpcUtils "github.com/grafana/grafana/pkg/storage/unified/resource/grpc" grpcUtils "github.com/grafana/grafana/pkg/storage/unified/resource/grpc"
@ -21,6 +22,7 @@ import (
type ResourceClient interface { type ResourceClient interface {
ResourceStoreClient ResourceStoreClient
ResourceIndexClient ResourceIndexClient
RepositoryIndexClient
BlobStoreClient BlobStoreClient
DiagnosticsClient DiagnosticsClient
} }
@ -29,6 +31,7 @@ type ResourceClient interface {
type resourceClient struct { type resourceClient struct {
ResourceStoreClient ResourceStoreClient
ResourceIndexClient ResourceIndexClient
RepositoryIndexClient
BlobStoreClient BlobStoreClient
DiagnosticsClient DiagnosticsClient
} }
@ -36,10 +39,11 @@ type resourceClient struct {
func NewLegacyResourceClient(channel *grpc.ClientConn) ResourceClient { func NewLegacyResourceClient(channel *grpc.ClientConn) ResourceClient {
cc := grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor) cc := grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)
return &resourceClient{ return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc), ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc), ResourceIndexClient: NewResourceIndexClient(cc),
BlobStoreClient: NewBlobStoreClient(cc), RepositoryIndexClient: NewRepositoryIndexClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc), BlobStoreClient: NewBlobStoreClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
} }
} }
@ -51,6 +55,7 @@ func NewLocalResourceClient(server ResourceServer) ResourceClient {
for _, desc := range []*grpc.ServiceDesc{ for _, desc := range []*grpc.ServiceDesc{
&ResourceStore_ServiceDesc, &ResourceStore_ServiceDesc,
&ResourceIndex_ServiceDesc, &ResourceIndex_ServiceDesc,
&RepositoryIndex_ServiceDesc,
&BlobStore_ServiceDesc, &BlobStore_ServiceDesc,
&Diagnostics_ServiceDesc, &Diagnostics_ServiceDesc,
} { } {
@ -72,10 +77,11 @@ func NewLocalResourceClient(server ResourceServer) ResourceClient {
cc := grpchan.InterceptClientConn(channel, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor) cc := grpchan.InterceptClientConn(channel, clientInt.UnaryClientInterceptor, clientInt.StreamClientInterceptor)
return &resourceClient{ return &resourceClient{
ResourceStoreClient: NewResourceStoreClient(cc), ResourceStoreClient: NewResourceStoreClient(cc),
ResourceIndexClient: NewResourceIndexClient(cc), ResourceIndexClient: NewResourceIndexClient(cc),
BlobStoreClient: NewBlobStoreClient(cc), RepositoryIndexClient: NewRepositoryIndexClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc), BlobStoreClient: NewBlobStoreClient(cc),
DiagnosticsClient: NewDiagnosticsClient(cc),
} }
} }

View File

@ -102,7 +102,7 @@ type IndexableDocument struct {
References ResourceReferences `json:"reference,omitempty"` References ResourceReferences `json:"reference,omitempty"`
// When the resource is managed by an upstream repository // When the resource is managed by an upstream repository
RepoInfo *utils.ResourceRepositoryInfo `json:"repository,omitempty"` RepoInfo *utils.ResourceRepositoryInfo `json:"repo,omitempty"`
} }
func (m *IndexableDocument) Type() string { func (m *IndexableDocument) Type() string {
@ -261,8 +261,11 @@ const SEARCH_FIELD_CREATED = "created"
const SEARCH_FIELD_CREATED_BY = "createdBy" const SEARCH_FIELD_CREATED_BY = "createdBy"
const SEARCH_FIELD_UPDATED = "updated" const SEARCH_FIELD_UPDATED = "updated"
const SEARCH_FIELD_UPDATED_BY = "updatedBy" const SEARCH_FIELD_UPDATED_BY = "updatedBy"
const SEARCH_FIELD_REPOSITORY = "repository"
const SEARCH_FIELD_REPOSITORY_HASH = "repository_hash" const SEARCH_FIELD_REPOSITORY_NAME = "repo.name"
const SEARCH_FIELD_REPOSITORY_PATH = "repo.path"
const SEARCH_FIELD_REPOSITORY_HASH = "repo.hash"
const SEARCH_FIELD_REPOSITORY_TIME = "repo.time"
const SEARCH_FIELD_SCORE = "_score" // the match score const SEARCH_FIELD_SCORE = "_score" // the match score
const SEARCH_FIELD_EXPLAIN = "_explain" // score explanation as JSON object const SEARCH_FIELD_EXPLAIN = "_explain" // score explanation as JSON object

View File

@ -40,7 +40,7 @@ func TestStandardDocumentBuilder(t *testing.T) {
"createdBy": "user:ABC", "createdBy": "user:ABC",
"updatedBy": "user:XYZ", "updatedBy": "user:XYZ",
"name": "test1", "name": "test1",
"repository": { "repo": {
"name": "SQL", "name": "SQL",
"path": "15", "path": "15",
"hash": "xyz" "hash": "xyz"

File diff suppressed because it is too large Load Diff

View File

@ -470,54 +470,76 @@ message HistoryResponse {
ErrorResult error = 4; ErrorResult error = 4;
} }
message OriginRequest { // List items within a resource type & repository name
// Access control is managed above this request
message ListRepositoryObjectsRequest {
// Starting from the requested page (other query parameters must match!) // Starting from the requested page (other query parameters must match!)
string next_page_token = 1; string next_page_token = 1;
// All results exist within this resource namespace+name
ResourceKey key = 2;
// The name of the repository
string name = 3;
// Maximum number of items to return // Maximum number of items to return
int64 limit = 2; int64 limit = 4;
// Resource identifier
ResourceKey key = 3;
// List the deleted values (eg, show trash)
string origin = 4;
} }
message ResourceOriginInfo { message ListRepositoryObjectsResponse {
// The resource message Item {
ResourceKey key = 1; // The resource object key
ResourceKey object = 1;
// Size of the full resource body // Hash for the resource
int32 resource_size = 2; string path = 2;
// Hash for the resource // Verification hash from the origin
string resource_hash = 3; string hash = 3;
// The origin name // Change time from the origin
string origin = 4; int64 time = 5;
// Path on the origin // Title inside the payload
string path = 5; string title = 6;
// Verification hash from the origin // The name of the folder in metadata
string hash = 6; string folder = 7;
}
// Change time from the origin // Item iterator
int64 timestamp = 7; repeated Item items = 1;
}
message OriginResponse {
repeated ResourceOriginInfo items = 1;
// More results exist... pass this in the next request // More results exist... pass this in the next request
string next_page_token = 2; string next_page_token = 2;
// ResourceVersion of the list response // Error details
int64 resource_version = 3; ErrorResult error = 3;
}
// Count the items that exist with
message CountRepositoryObjectsRequest {
// Namespace (tenant)
string namespace = 1;
// The name of the repository
// (eventually empty to count across all repositories)
string repository = 2;
}
// Count the items that exist with
message CountRepositoryObjectsResponse {
message KindCount {
string group = 1;
string resource = 2;
int64 count = 3;
}
// Stats keyed by the repository name
map<string,KindCount> stats = 1;
// Error details // Error details
ErrorResult error = 4; ErrorResult error = 2;
} }
message HealthCheckRequest { message HealthCheckRequest {
@ -609,7 +631,7 @@ message ResourceTableColumnDefinition {
// Defines the column type. In k8s, this will resolve into both the type and format fields // Defines the column type. In k8s, this will resolve into both the type and format fields
ColumnType type = 2; ColumnType type = 2;
// The value is an arry of given type // The value is an array of given type
bool is_array = 3; bool is_array = 3;
// description is a human readable description of this column. // description is a human readable description of this column.
@ -774,9 +796,16 @@ service ResourceIndex {
// Show resource history (and trash) // Show resource history (and trash)
rpc History(HistoryRequest) returns (HistoryResponse); rpc History(HistoryRequest) returns (HistoryResponse);
}
// Used for efficient provisioning // Query repository info from the search index.
rpc Origin(OriginRequest) returns (OriginResponse); // Results access control is based on access to the repository *not* the items
service RepositoryIndex {
// Describe how many resources of each type exist within a repository
rpc CountRepositoryObjects(CountRepositoryObjectsRequest) returns (CountRepositoryObjectsResponse);
// List the resources of a specific kind within a repository
rpc ListRepositoryObjects(ListRepositoryObjectsRequest) returns (ListRepositoryObjectsResponse);
} }
service BlobStore { service BlobStore {

View File

@ -389,7 +389,6 @@ const (
ResourceIndex_Search_FullMethodName = "/resource.ResourceIndex/Search" ResourceIndex_Search_FullMethodName = "/resource.ResourceIndex/Search"
ResourceIndex_GetStats_FullMethodName = "/resource.ResourceIndex/GetStats" ResourceIndex_GetStats_FullMethodName = "/resource.ResourceIndex/GetStats"
ResourceIndex_History_FullMethodName = "/resource.ResourceIndex/History" ResourceIndex_History_FullMethodName = "/resource.ResourceIndex/History"
ResourceIndex_Origin_FullMethodName = "/resource.ResourceIndex/Origin"
) )
// ResourceIndexClient is the client API for ResourceIndex service. // ResourceIndexClient is the client API for ResourceIndex service.
@ -404,8 +403,6 @@ type ResourceIndexClient interface {
GetStats(ctx context.Context, in *ResourceStatsRequest, opts ...grpc.CallOption) (*ResourceStatsResponse, error) GetStats(ctx context.Context, in *ResourceStatsRequest, opts ...grpc.CallOption) (*ResourceStatsResponse, error)
// Show resource history (and trash) // Show resource history (and trash)
History(ctx context.Context, in *HistoryRequest, opts ...grpc.CallOption) (*HistoryResponse, error) History(ctx context.Context, in *HistoryRequest, opts ...grpc.CallOption) (*HistoryResponse, error)
// Used for efficient provisioning
Origin(ctx context.Context, in *OriginRequest, opts ...grpc.CallOption) (*OriginResponse, error)
} }
type resourceIndexClient struct { type resourceIndexClient struct {
@ -446,16 +443,6 @@ func (c *resourceIndexClient) History(ctx context.Context, in *HistoryRequest, o
return out, nil return out, nil
} }
func (c *resourceIndexClient) Origin(ctx context.Context, in *OriginRequest, opts ...grpc.CallOption) (*OriginResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(OriginResponse)
err := c.cc.Invoke(ctx, ResourceIndex_Origin_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// ResourceIndexServer is the server API for ResourceIndex service. // ResourceIndexServer is the server API for ResourceIndex service.
// All implementations should embed UnimplementedResourceIndexServer // All implementations should embed UnimplementedResourceIndexServer
// for forward compatibility // for forward compatibility
@ -468,8 +455,6 @@ type ResourceIndexServer interface {
GetStats(context.Context, *ResourceStatsRequest) (*ResourceStatsResponse, error) GetStats(context.Context, *ResourceStatsRequest) (*ResourceStatsResponse, error)
// Show resource history (and trash) // Show resource history (and trash)
History(context.Context, *HistoryRequest) (*HistoryResponse, error) History(context.Context, *HistoryRequest) (*HistoryResponse, error)
// Used for efficient provisioning
Origin(context.Context, *OriginRequest) (*OriginResponse, error)
} }
// UnimplementedResourceIndexServer should be embedded to have forward compatible implementations. // UnimplementedResourceIndexServer should be embedded to have forward compatible implementations.
@ -485,9 +470,6 @@ func (UnimplementedResourceIndexServer) GetStats(context.Context, *ResourceStats
func (UnimplementedResourceIndexServer) History(context.Context, *HistoryRequest) (*HistoryResponse, error) { func (UnimplementedResourceIndexServer) History(context.Context, *HistoryRequest) (*HistoryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method History not implemented") return nil, status.Errorf(codes.Unimplemented, "method History not implemented")
} }
func (UnimplementedResourceIndexServer) Origin(context.Context, *OriginRequest) (*OriginResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Origin not implemented")
}
// UnsafeResourceIndexServer may be embedded to opt out of forward compatibility for this service. // 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 // Use of this interface is not recommended, as added methods to ResourceIndexServer will
@ -554,24 +536,6 @@ func _ResourceIndex_History_Handler(srv interface{}, ctx context.Context, dec fu
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _ResourceIndex_Origin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(OriginRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResourceIndexServer).Origin(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ResourceIndex_Origin_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResourceIndexServer).Origin(ctx, req.(*OriginRequest))
}
return interceptor(ctx, in, info, handler)
}
// ResourceIndex_ServiceDesc is the grpc.ServiceDesc for ResourceIndex service. // ResourceIndex_ServiceDesc is the grpc.ServiceDesc for ResourceIndex service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@ -591,9 +555,142 @@ var ResourceIndex_ServiceDesc = grpc.ServiceDesc{
MethodName: "History", MethodName: "History",
Handler: _ResourceIndex_History_Handler, Handler: _ResourceIndex_History_Handler,
}, },
},
Streams: []grpc.StreamDesc{},
Metadata: "resource.proto",
}
const (
RepositoryIndex_CountRepositoryObjects_FullMethodName = "/resource.RepositoryIndex/CountRepositoryObjects"
RepositoryIndex_ListRepositoryObjects_FullMethodName = "/resource.RepositoryIndex/ListRepositoryObjects"
)
// RepositoryIndexClient is the client API for RepositoryIndex 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.
//
// Query repository info from the search index.
// Results access control is based on access to the repository *not* the items
type RepositoryIndexClient interface {
// Describe how many resources of each type exist within a repository
CountRepositoryObjects(ctx context.Context, in *CountRepositoryObjectsRequest, opts ...grpc.CallOption) (*CountRepositoryObjectsResponse, error)
// List the resources of a specific kind within a repository
ListRepositoryObjects(ctx context.Context, in *ListRepositoryObjectsRequest, opts ...grpc.CallOption) (*ListRepositoryObjectsResponse, error)
}
type repositoryIndexClient struct {
cc grpc.ClientConnInterface
}
func NewRepositoryIndexClient(cc grpc.ClientConnInterface) RepositoryIndexClient {
return &repositoryIndexClient{cc}
}
func (c *repositoryIndexClient) CountRepositoryObjects(ctx context.Context, in *CountRepositoryObjectsRequest, opts ...grpc.CallOption) (*CountRepositoryObjectsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CountRepositoryObjectsResponse)
err := c.cc.Invoke(ctx, RepositoryIndex_CountRepositoryObjects_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *repositoryIndexClient) ListRepositoryObjects(ctx context.Context, in *ListRepositoryObjectsRequest, opts ...grpc.CallOption) (*ListRepositoryObjectsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListRepositoryObjectsResponse)
err := c.cc.Invoke(ctx, RepositoryIndex_ListRepositoryObjects_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// RepositoryIndexServer is the server API for RepositoryIndex service.
// All implementations should embed UnimplementedRepositoryIndexServer
// for forward compatibility
//
// Query repository info from the search index.
// Results access control is based on access to the repository *not* the items
type RepositoryIndexServer interface {
// Describe how many resources of each type exist within a repository
CountRepositoryObjects(context.Context, *CountRepositoryObjectsRequest) (*CountRepositoryObjectsResponse, error)
// List the resources of a specific kind within a repository
ListRepositoryObjects(context.Context, *ListRepositoryObjectsRequest) (*ListRepositoryObjectsResponse, error)
}
// UnimplementedRepositoryIndexServer should be embedded to have forward compatible implementations.
type UnimplementedRepositoryIndexServer struct {
}
func (UnimplementedRepositoryIndexServer) CountRepositoryObjects(context.Context, *CountRepositoryObjectsRequest) (*CountRepositoryObjectsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountRepositoryObjects not implemented")
}
func (UnimplementedRepositoryIndexServer) ListRepositoryObjects(context.Context, *ListRepositoryObjectsRequest) (*ListRepositoryObjectsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRepositoryObjects not implemented")
}
// UnsafeRepositoryIndexServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to RepositoryIndexServer will
// result in compilation errors.
type UnsafeRepositoryIndexServer interface {
mustEmbedUnimplementedRepositoryIndexServer()
}
func RegisterRepositoryIndexServer(s grpc.ServiceRegistrar, srv RepositoryIndexServer) {
s.RegisterService(&RepositoryIndex_ServiceDesc, srv)
}
func _RepositoryIndex_CountRepositoryObjects_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CountRepositoryObjectsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RepositoryIndexServer).CountRepositoryObjects(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: RepositoryIndex_CountRepositoryObjects_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RepositoryIndexServer).CountRepositoryObjects(ctx, req.(*CountRepositoryObjectsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RepositoryIndex_ListRepositoryObjects_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListRepositoryObjectsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RepositoryIndexServer).ListRepositoryObjects(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: RepositoryIndex_ListRepositoryObjects_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RepositoryIndexServer).ListRepositoryObjects(ctx, req.(*ListRepositoryObjectsRequest))
}
return interceptor(ctx, in, info, handler)
}
// RepositoryIndex_ServiceDesc is the grpc.ServiceDesc for RepositoryIndex service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var RepositoryIndex_ServiceDesc = grpc.ServiceDesc{
ServiceName: "resource.RepositoryIndex",
HandlerType: (*RepositoryIndexServer)(nil),
Methods: []grpc.MethodDesc{
{ {
MethodName: "Origin", MethodName: "CountRepositoryObjects",
Handler: _ResourceIndex_Origin_Handler, Handler: _RepositoryIndex_CountRepositoryObjects_Handler,
},
{
MethodName: "ListRepositoryObjects",
Handler: _RepositoryIndex_ListRepositoryObjects_Handler,
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},

View File

@ -43,9 +43,11 @@ type ResourceIndex interface {
// When working with federated queries, the additional indexes will be passed in explicitly // When working with federated queries, the additional indexes will be passed in explicitly
Search(ctx context.Context, access authz.AccessClient, req *ResourceSearchRequest, federate []ResourceIndex) (*ResourceSearchResponse, error) Search(ctx context.Context, access authz.AccessClient, req *ResourceSearchRequest, federate []ResourceIndex) (*ResourceSearchResponse, error)
// Execute an origin query -- access control is not not checked for each item // List within an response
// NOTE: this will likely be used for provisioning, or it will be removed ListRepositoryObjects(ctx context.Context, req *ListRepositoryObjectsRequest) (*ListRepositoryObjectsResponse, error)
Origin(ctx context.Context, req *OriginRequest) (*OriginResponse, error)
// Counts the values in a repo
CountRepositoryObjects(ctx context.Context) (map[string]int64, error)
// Get the number of documents in the index // Get the number of documents in the index
DocCount(ctx context.Context, folder string) (int64, error) DocCount(ctx context.Context, folder string) (int64, error)
@ -93,7 +95,8 @@ type searchSupport struct {
} }
var ( var (
_ ResourceIndexServer = (*searchSupport)(nil) _ ResourceIndexServer = (*searchSupport)(nil)
_ RepositoryIndexServer = (*searchSupport)(nil)
) )
func newSearchSupport(opts SearchOptions, storage StorageBackend, access authz.AccessClient, blob BlobSupport, tracer trace.Tracer) (support *searchSupport, err error) { func newSearchSupport(opts SearchOptions, storage StorageBackend, access authz.AccessClient, blob BlobSupport, tracer trace.Tracer) (support *searchSupport, err error) {
@ -131,12 +134,25 @@ func newSearchSupport(opts SearchOptions, storage StorageBackend, access authz.A
// History implements ResourceIndexServer. // History implements ResourceIndexServer.
func (s *searchSupport) History(context.Context, *HistoryRequest) (*HistoryResponse, error) { func (s *searchSupport) History(context.Context, *HistoryRequest) (*HistoryResponse, error) {
return nil, fmt.Errorf("not implemented yet... likely should not be the serarch server") return nil, fmt.Errorf("not implemented yet... likely should not be the search server")
} }
// Origin implements ResourceIndexServer. func (s *searchSupport) ListRepositoryObjects(ctx context.Context, req *ListRepositoryObjectsRequest) (*ListRepositoryObjectsResponse, error) {
func (s *searchSupport) Origin(context.Context, *OriginRequest) (*OriginResponse, error) { idx, err := s.getOrCreateIndex(ctx, NamespacedResource{
return nil, fmt.Errorf("TBD.. rename to repository") Group: req.Key.Group,
Namespace: req.Key.Namespace,
Resource: req.Key.Resource,
})
if err != nil {
return &ListRepositoryObjectsResponse{
Error: AsErrorResult(err),
}, nil
}
return idx.ListRepositoryObjects(ctx, req)
}
func (s *searchSupport) CountRepositoryObjects(context.Context, *CountRepositoryObjectsRequest) (*CountRepositoryObjectsResponse, error) {
return nil, fmt.Errorf("not implemented yet... requires iterating kinds")
} }
// Search implements ResourceIndexServer. // Search implements ResourceIndexServer.

View File

@ -29,6 +29,7 @@ import (
type ResourceServer interface { type ResourceServer interface {
ResourceStoreServer ResourceStoreServer
ResourceIndexServer ResourceIndexServer
RepositoryIndexServer
BlobStoreServer BlobStoreServer
DiagnosticsServer DiagnosticsServer
} }
@ -1073,9 +1074,12 @@ func (s *server) History(ctx context.Context, req *HistoryRequest) (*HistoryResp
return s.search.History(ctx, req) return s.search.History(ctx, req)
} }
// Origin implements ResourceServer. func (s *server) ListRepositoryObjects(ctx context.Context, req *ListRepositoryObjectsRequest) (*ListRepositoryObjectsResponse, error) {
func (s *server) Origin(ctx context.Context, req *OriginRequest) (*OriginResponse, error) { return s.search.ListRepositoryObjects(ctx, req)
return s.search.Origin(ctx, req) }
func (s *server) CountRepositoryObjects(ctx context.Context, req *CountRepositoryObjectsRequest) (*CountRepositoryObjectsResponse, error) {
return s.search.CountRepositoryObjects(ctx, req)
} }
// IsHealthy implements ResourceServer. // IsHealthy implements ResourceServer.

View File

@ -245,9 +245,108 @@ func (b *bleveIndex) Flush() (err error) {
return err return err
} }
// Origin implements resource.DocumentIndex. func (b *bleveIndex) ListRepositoryObjects(ctx context.Context, req *resource.ListRepositoryObjectsRequest) (*resource.ListRepositoryObjectsResponse, error) {
func (b *bleveIndex) Origin(ctx context.Context, req *resource.OriginRequest) (*resource.OriginResponse, error) { if req.NextPageToken != "" {
panic("unimplemented") return nil, fmt.Errorf("next page not implemented yet")
}
size := 1000000000 // big number
if req.Limit > 0 {
size = int(req.Limit)
}
found, err := b.index.SearchInContext(ctx, &bleve.SearchRequest{
Query: &query.TermQuery{
Term: req.Name,
FieldVal: resource.SEARCH_FIELD_REPOSITORY_NAME,
},
Fields: []string{
resource.SEARCH_FIELD_TITLE,
resource.SEARCH_FIELD_FOLDER,
resource.SEARCH_FIELD_REPOSITORY_NAME,
resource.SEARCH_FIELD_REPOSITORY_PATH,
resource.SEARCH_FIELD_REPOSITORY_HASH,
resource.SEARCH_FIELD_REPOSITORY_TIME,
},
Sort: search.SortOrder{
&search.SortField{
Field: resource.SEARCH_FIELD_REPOSITORY_PATH,
Type: search.SortFieldAsString,
Desc: false,
},
},
Size: size,
From: 0, // TODO! next page token!!!
})
if err != nil {
return nil, err
}
asString := func(v any) string {
if v == nil {
return ""
}
str, ok := v.(string)
if ok {
return str
}
return fmt.Sprintf("%v", v)
}
asTime := func(v any) int64 {
if v == nil {
return 0
}
intV, ok := v.(int64)
if ok {
return intV
}
str, ok := v.(string)
if ok {
t, _ := time.Parse(time.RFC3339, str)
return t.UnixMilli()
}
return 0
}
rsp := &resource.ListRepositoryObjectsResponse{}
for _, hit := range found.Hits {
item := &resource.ListRepositoryObjectsResponse_Item{
Object: &resource.ResourceKey{},
Hash: asString(hit.Fields[resource.SEARCH_FIELD_REPOSITORY_HASH]),
Path: asString(hit.Fields[resource.SEARCH_FIELD_REPOSITORY_PATH]),
Time: asTime(hit.Fields[resource.SEARCH_FIELD_REPOSITORY_TIME]),
Title: asString(hit.Fields[resource.SEARCH_FIELD_TITLE]),
Folder: asString(hit.Fields[resource.SEARCH_FIELD_FOLDER]),
}
err := item.Object.ReadSearchID(hit.ID)
if err != nil {
return nil, err
}
rsp.Items = append(rsp.Items, item)
}
return rsp, nil
}
func (b *bleveIndex) CountRepositoryObjects(ctx context.Context) (map[string]int64, error) {
found, err := b.index.SearchInContext(ctx, &bleve.SearchRequest{
Query: bleve.NewMatchAllQuery(),
Size: 0,
Facets: bleve.FacetsRequest{
"count": bleve.NewFacetRequest(resource.SEARCH_FIELD_REPOSITORY_NAME, 1000), // typically less then 5
},
})
if err != nil {
return nil, err
}
rsp := make(map[string]int64)
f, ok := found.Facets["count"]
if ok && f.Terms != nil {
for _, v := range f.Terms.Terms() {
rsp[v.Term] = int64(v.Count)
}
}
return rsp, nil
} }
// Search implements resource.DocumentIndex. // Search implements resource.DocumentIndex.

View File

@ -67,17 +67,38 @@ func getBleveDocMappings(_ resource.SearchableDocumentFields) *mapping.DocumentM
} }
mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_FOLDER, folderMapping) mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_FOLDER, folderMapping)
repoMapping := &mapping.FieldMapping{ // Repositories
Name: resource.SEARCH_FIELD_REPOSITORY, repo := bleve.NewDocumentStaticMapping()
repo.AddFieldMappingsAt("name", &mapping.FieldMapping{
Name: "name",
Type: "text", Type: "text",
Analyzer: keyword.Name, Analyzer: keyword.Name,
Store: true, Store: true,
Index: true, Index: true,
IncludeTermVectors: false, IncludeTermVectors: false,
IncludeInAll: true, IncludeInAll: true,
DocValues: true, })
} repo.AddFieldMappingsAt("path", &mapping.FieldMapping{
mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_REPOSITORY, repoMapping) Name: "path",
Type: "text",
Analyzer: keyword.Name,
Store: true,
Index: true,
IncludeTermVectors: false,
IncludeInAll: true,
})
repo.AddFieldMappingsAt("hash", &mapping.FieldMapping{
Name: "hash",
Type: "text",
Analyzer: keyword.Name,
Store: true,
Index: true,
IncludeTermVectors: false,
IncludeInAll: true,
})
repo.AddFieldMappingsAt("time", mapping.NewDateTimeFieldMapping())
mapper.AddSubDocumentMapping("repo", repo)
labelMapper := bleve.NewDocumentMapping() labelMapper := bleve.NewDocumentMapping()
mapper.AddSubDocumentMapping(resource.SEARCH_FIELD_LABELS, labelMapper) mapper.AddSubDocumentMapping(resource.SEARCH_FIELD_LABELS, labelMapper)

View File

@ -26,9 +26,10 @@ func TestDocumentMapping(t *testing.T) {
}, },
RV: 1234, RV: 1234,
RepoInfo: &utils.ResourceRepositoryInfo{ RepoInfo: &utils.ResourceRepositoryInfo{
Name: "nnn", Name: "nnn",
Path: "ppp", Path: "ppp",
Hash: "hhh", Hash: "hhh",
Timestamp: asTimePointer(1234),
}, },
} }
@ -42,5 +43,5 @@ func TestDocumentMapping(t *testing.T) {
fmt.Printf("DOC: fields %d\n", len(doc.Fields)) fmt.Printf("DOC: fields %d\n", len(doc.Fields))
fmt.Printf("DOC: size %d\n", doc.Size()) fmt.Printf("DOC: size %d\n", doc.Size())
require.Equal(t, 12, len(doc.Fields)) require.Equal(t, 13, len(doc.Fields))
} }

View File

@ -3,9 +3,11 @@ package search
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -82,6 +84,12 @@ func TestBleveBackend(t *testing.T) {
utils.LabelKeyDeprecatedInternalID: "10", // nolint:staticcheck utils.LabelKeyDeprecatedInternalID: "10", // nolint:staticcheck
}, },
Tags: []string{"aa", "bb"}, Tags: []string{"aa", "bb"},
RepoInfo: &utils.ResourceRepositoryInfo{
Name: "repo-1",
Path: "path/to/aaa.json",
Hash: "xyz",
Timestamp: asTimePointer(1609462800000), // 2021
},
}) })
_ = index.Write(&resource.IndexableDocument{ _ = index.Write(&resource.IndexableDocument{
RV: 2, RV: 2,
@ -105,6 +113,12 @@ func TestBleveBackend(t *testing.T) {
"region": "east", "region": "east",
utils.LabelKeyDeprecatedInternalID: "11", // nolint:staticcheck utils.LabelKeyDeprecatedInternalID: "11", // nolint:staticcheck
}, },
RepoInfo: &utils.ResourceRepositoryInfo{
Name: "repo-1",
Path: "path/to/bbb.json",
Hash: "hijk",
Timestamp: asTimePointer(1640998800000), // 2022
},
}) })
_ = index.Write(&resource.IndexableDocument{ _ = index.Write(&resource.IndexableDocument{
RV: 3, RV: 3,
@ -119,7 +133,8 @@ func TestBleveBackend(t *testing.T) {
TitleSort: "ccc (dash)", TitleSort: "ccc (dash)",
Folder: "zzz", Folder: "zzz",
RepoInfo: &utils.ResourceRepositoryInfo{ RepoInfo: &utils.ResourceRepositoryInfo{
Name: "r0", Name: "repo2",
Path: "path/in/repo2.yaml",
}, },
Fields: map[string]any{ Fields: map[string]any{
DASHBOARD_LEGACY_ID: 12, DASHBOARD_LEGACY_ID: 12,
@ -201,6 +216,53 @@ func TestBleveBackend(t *testing.T) {
rsp.Results.Rows[0].Key.Name, rsp.Results.Rows[0].Key.Name,
rsp.Results.Rows[1].Key.Name, rsp.Results.Rows[1].Key.Name,
}) })
// Now look for repositories
found, err := index.ListRepositoryObjects(ctx, &resource.ListRepositoryObjectsRequest{
Name: "repo-1",
Limit: 100,
})
require.NoError(t, err)
jj, err := json.MarshalIndent(found, "", " ")
require.NoError(t, err)
fmt.Printf("%s\n", string(jj))
require.JSONEq(t, `{
"items": [
{
"object": {
"namespace": "ns",
"group": "dashboard.grafana.app",
"resource": "dashboards",
"name": "aaa"
},
"path": "path/to/aaa.json",
"hash": "xyz",
"time": 1609462800000,
"title": "aaa (dash)",
"folder": "xxx"
},
{
"object": {
"namespace": "ns",
"group": "dashboard.grafana.app",
"resource": "dashboards",
"name": "bbb"
},
"path": "path/to/bbb.json",
"hash": "hijk",
"time": 1640998800000,
"title": "bbb (dash)",
"folder": "xxx"
}
]
}`, string(jj))
counts, err := index.CountRepositoryObjects(ctx)
require.NoError(t, err)
require.Equal(t, map[string]int64{
"repo-1": 2,
"repo2": 1,
}, counts)
}) })
t.Run("build folders", func(t *testing.T) { t.Run("build folders", func(t *testing.T) {
@ -222,6 +284,12 @@ func TestBleveBackend(t *testing.T) {
}, },
Title: "zzz (folder)", Title: "zzz (folder)",
TitleSort: "zzz (folder)", TitleSort: "zzz (folder)",
RepoInfo: &utils.ResourceRepositoryInfo{
Name: "repo-1",
Path: "path/to/folder.json",
Hash: "xxxx",
Timestamp: asTimePointer(300),
},
}) })
_ = index.Write(&resource.IndexableDocument{ _ = index.Write(&resource.IndexableDocument{
RV: 2, RV: 2,
@ -327,3 +395,11 @@ func TestBleveBackend(t *testing.T) {
}`, string(disp)) }`, string(disp))
}) })
} }
func asTimePointer(milli int64) *time.Time {
if milli > 0 {
t := time.UnixMilli(milli)
return &t
}
return nil
}

View File

@ -11,7 +11,7 @@
"title_sort": "test-aaa", "title_sort": "test-aaa",
"created": 1730490142000, "created": 1730490142000,
"createdBy": "user:1", "createdBy": "user:1",
"repository": { "repo": {
"name": "SQL" "name": "SQL"
} }
} }

View File

@ -11,7 +11,7 @@
"title_sort": "test-bbb", "title_sort": "test-bbb",
"created": 1730490142000, "created": 1730490142000,
"createdBy": "user:1", "createdBy": "user:1",
"repository": { "repo": {
"name": "SQL" "name": "SQL"
} }
} }

View File

@ -11,7 +11,7 @@
"title_sort": "test aaa", "title_sort": "test aaa",
"created": 1731336353000, "created": 1731336353000,
"createdBy": "user:t000000001", "createdBy": "user:t000000001",
"repository": { "repo": {
"name": "UI", "name": "UI",
"path": "/playlists/new", "path": "/playlists/new",
"hash": "Grafana v11.4.0-pre (c0de407fee)" "hash": "Grafana v11.4.0-pre (c0de407fee)"

View File

@ -11,7 +11,7 @@
"title_sort": "test aaa", "title_sort": "test aaa",
"created": 1706690655000, "created": 1706690655000,
"createdBy": "user:abc", "createdBy": "user:abc",
"repository": { "repo": {
"name": "SQL" "name": "SQL"
} }
} }

View File

@ -2,6 +2,7 @@ package sql
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resource"
@ -62,14 +63,12 @@ func (b *backend) History(context.Context, *resource.HistoryRequest) (*resource.
}, nil }, nil
} }
// Origin implements resource.ResourceIndexServer. func (b *backend) RepositoryList(ctx context.Context, req *resource.ListRepositoryObjectsRequest) (*resource.ListRepositoryObjectsResponse, error) {
func (b *backend) Origin(context.Context, *resource.OriginRequest) (*resource.OriginResponse, error) { return nil, fmt.Errorf("SQL backend does not implement RepositoryList")
return &resource.OriginResponse{ }
Error: &resource.ErrorResult{
Code: http.StatusNotImplemented, func (b *backend) RepositoryStats(context.Context, *resource.CountRepositoryObjectsRequest) (*resource.CountRepositoryObjectsResponse, error) {
Message: "SQL backend does not implement Origin", return nil, fmt.Errorf("SQL backend does not implement RepositoryStats")
},
}, nil
} }
// Search implements resource.ResourceIndexServer. // Search implements resource.ResourceIndexServer.