Files
grafana/pkg/registry/apis/search/register.go
owensmallwood 612b864772 Indexing PoC: Add search/browse (#94126)
* adds Filter gRPC and make protobuf

* adds route for querying the filter gRPC

* wires up Filter gRPC call

* [WIP] index from start

* renames gRPC endpoint to "Search"

* adds /apis/search route into k8s routes. Hacky for now.

* updates readme - wrong casing

* adds feature toggle for unified storage search

* hides US search behind feature flag. Clean up print statements.

* removes indexer - will be added in another PR

* Search: Add API Builder

* adds required method

* implementing UpdateAPIGroupInfo (WIP)

* adds groupversion

* commenting out for now

* remove unneeded code from experimenting and update register.go to match interface required

* list resources and load into index

* pass context

* namespaces search route

* lint

* watch

* add todo

* add todo

* merge

* cleanup

* add todo

* gen protobuf

* lint; fix migration issue

* Updates index mapping function to map unified storage object Value

* Changes Index() to pointer receiver - fixes panic

* add delete

* cleanup

* gets search/browse functioning. Results show up as base64 encoded. Still a WIP.

* Doesnt json re-encode gRPC response in search handler

* add kind to SearchRequest proto

* Updates query interface to be more generic. Make proto. Parses query params in api server.

* make protobuf

* removes unused method and imports

* Returns all indexed fields in search results. Adds pagination support (limit + offset).

* remove comment

* remove unused struct

* gets tenant in search k8s api handler

* adds hardcoded spec field mappings - starting with playlists

* adds all spec fields to search results

* moved helper function for field mappings into index

* only includes allowed spec fields in search results

* cleans up error handling

* removes debug log

---------

Co-authored-by: leonorfmartins <leonorfmartins@gmail.com>
Co-authored-by: Todd Treece <todd.treece@grafana.com>
Co-authored-by: Scott Lepper <scott.lepper@gmail.com>
2024-10-09 11:20:05 -06:00

151 lines
4.5 KiB
Go

package search
import (
"encoding/json"
"net/http"
"net/url"
"strconv"
"github.com/grafana/grafana/pkg/api/response"
request2 "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/setting"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
genericapiserver "k8s.io/apiserver/pkg/server"
common "k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/storage/unified/resource"
)
var _ builder.APIGroupBuilder = (*SearchAPIBuilder)(nil)
type SearchAPIBuilder struct {
unified resource.ResourceClient
namespacer request2.NamespaceMapper
}
func NewSearchAPIBuilder(
unified resource.ResourceClient,
cfg *setting.Cfg,
) (*SearchAPIBuilder, error) {
return &SearchAPIBuilder{
unified: unified,
namespacer: request2.GetNamespaceMapper(cfg),
}, nil
}
func RegisterAPIService(
features featuremgmt.FeatureToggles,
apiregistration builder.APIRegistrar,
unified resource.ResourceClient,
cfg *setting.Cfg,
) (*SearchAPIBuilder, error) {
if !(features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageSearch) || features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs)) {
return nil, nil
}
builder, err := NewSearchAPIBuilder(unified, cfg)
apiregistration.RegisterAPI(builder)
return builder, err
}
func (b *SearchAPIBuilder) GetGroupVersion() schema.GroupVersion {
return schema.GroupVersion{Group: "search.grafana.app", Version: "v0alpha1"}
}
func (b *SearchAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
return nil
}
func (b *SearchAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{}
}
}
func (b *SearchAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
return &builder.APIRoutes{
Namespace: []builder.APIRouteHandler{
{
Path: "search",
Spec: &spec3.PathProps{
Get: &spec3.Operation{
OperationProps: spec3.OperationProps{
Tags: []string{"Search"},
Summary: "Search",
Description: "Search for resources",
},
},
},
Handler: func(w http.ResponseWriter, r *http.Request) {
// get tenant
orgId, err := request2.OrgIDForList(r.Context())
if err != nil {
response.Error(500, "failed to get orgId", err)
}
tenant := b.namespacer(orgId)
queryParams, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
response.Error(500, "failed to parse query params", err)
}
// get limit and offset from query params
limit := 0
offset := 0
if queryParams.Has("limit") {
limit, _ = strconv.Atoi(queryParams.Get("limit"))
}
if queryParams.Has("offset") {
offset, _ = strconv.Atoi(queryParams.Get("offset"))
}
searchRequest := &resource.SearchRequest{
Tenant: tenant,
Kind: queryParams.Get("kind"),
QueryType: queryParams.Get("queryType"),
Query: queryParams.Get("query"),
Limit: int64(limit),
Offset: int64(offset),
}
res, err := b.unified.Search(r.Context(), searchRequest)
if err != nil {
response.Error(500, "search request failed", err)
}
// TODO need a nicer way of handling this
// the [][]byte response already contains the marshalled JSON, so we don't need to re-encode it
rawMessages := make([]json.RawMessage, len(res.GetItems()))
for i, item := range res.GetItems() {
rawMessages[i] = item.Value
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(rawMessages); err != nil {
response.Error(500, "failed to json encode raw response", err)
}
},
},
},
}
}
func (b *SearchAPIBuilder) GetAuthorizer() authorizer.Authorizer {
return nil
}
func (b *SearchAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
return oas, nil
}
func (b *SearchAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter, dualWriteBuilder grafanarest.DualWriteBuilder) error {
apiGroupInfo.PrioritizedVersions = []schema.GroupVersion{b.GetGroupVersion()}
return nil
}