Files
grafana/pkg/services/unifiedSearch/service.go

182 lines
6.0 KiB
Go

package unifiedSearch
import (
"context"
"errors"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/resource"
)
type StandardSearchService struct {
registry.BackgroundService
cfg *setting.Cfg
sql db.DB
ac accesscontrol.Service
orgService org.Service
userService user.Service
logger log.Logger
reIndexCh chan struct{}
features featuremgmt.FeatureToggles
resourceClient resource.ResourceClient
}
func (s *StandardSearchService) IsReady(ctx context.Context, orgId int64) IsSearchReadyResponse {
return IsSearchReadyResponse{IsReady: true}
}
func ProvideService(cfg *setting.Cfg, sql db.DB, entityEventStore store.EntityEventsService,
ac accesscontrol.Service, tracer tracing.Tracer, features featuremgmt.FeatureToggles, orgService org.Service,
userService user.Service, folderStore folder.Store, resourceClient resource.ResourceClient) SearchService {
logger := log.New("searchV3")
s := &StandardSearchService{
cfg: cfg,
sql: sql,
ac: ac,
logger: logger,
reIndexCh: make(chan struct{}, 1),
orgService: orgService,
userService: userService,
features: features,
resourceClient: resourceClient,
}
return s
}
func (s *StandardSearchService) IsDisabled() bool {
return !s.features.IsEnabledGlobally(featuremgmt.FlagPanelTitleSearch)
}
func (s *StandardSearchService) Run(ctx context.Context) error {
// TODO: implement this? ( copied from pkg/services/searchV2/service.go )
// orgQuery := &org.SearchOrgsQuery{}
// result, err := s.orgService.Search(ctx, orgQuery)
// if err != nil {
// return fmt.Errorf("can't get org list: %w", err)
// }
// orgIDs := make([]int64, 0, len(result))
// for _, org := range result {
// orgIDs = append(orgIDs, org.ID)
// }
// TODO: do we need to initialize the bleve index again ( should be initialized on startup )?
// return s.dashboardIndex.run(ctx, orgIDs, s.reIndexCh)
return nil
}
func (s *StandardSearchService) TriggerReIndex() {
select {
case s.reIndexCh <- struct{}{}:
default:
// channel is full => re-index will happen soon anyway.
}
}
func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backend.User, orgId int64) (*user.SignedInUser, error) {
// TODO: get user & user's permissions from the request context
var usr *user.SignedInUser
if s.cfg.AnonymousEnabled && backendUser.Email == "" && backendUser.Login == "" {
getOrg := org.GetOrgByNameQuery{Name: s.cfg.AnonymousOrgName}
orga, err := s.orgService.GetByName(ctx, &getOrg)
if err != nil {
s.logger.Error("Anonymous access organization error.", "org_name", s.cfg.AnonymousOrgName, "error", err)
return nil, err
}
usr = &user.SignedInUser{
OrgID: orga.ID,
OrgName: orga.Name,
OrgRole: org.RoleType(s.cfg.AnonymousOrgRole),
IsAnonymous: true,
}
} else {
getSignedInUserQuery := &user.GetSignedInUserQuery{
Login: backendUser.Login,
Email: backendUser.Email,
OrgID: orgId,
}
var err error
usr, err = s.userService.GetSignedInUser(ctx, getSignedInUserQuery)
if err != nil {
s.logger.Error("Error while retrieving user", "error", err, "email", backendUser.Email, "login", getSignedInUserQuery.Login)
return nil, errors.New("auth error")
}
if usr == nil {
s.logger.Error("No user found", "email", backendUser.Email)
return nil, errors.New("auth error")
}
}
if usr.Permissions == nil {
usr.Permissions = make(map[int64]map[string][]string)
}
if _, ok := usr.Permissions[orgId]; ok {
// permissions as part of the `s.sql.GetSignedInUser` query - return early
return usr, nil
}
// TODO: ensure this is cached
permissions, err := s.ac.GetUserPermissions(ctx, usr,
accesscontrol.Options{ReloadCache: false})
if err != nil {
s.logger.Error("Failed to retrieve user permissions", "error", err, "email", backendUser.Email)
return nil, errors.New("auth error")
}
usr.Permissions[orgId] = accesscontrol.GroupScopesByActionContext(ctx, permissions)
return usr, nil
}
func (s *StandardSearchService) DoQuery(ctx context.Context, user *backend.User, orgID int64, q Query) *backend.DataResponse {
signedInUser, err := s.getUser(ctx, user, orgID)
if err != nil {
return &backend.DataResponse{Error: err}
}
query := s.doQuery(ctx, signedInUser, orgID, q)
return query
}
func (s *StandardSearchService) doQuery(ctx context.Context, signedInUser *user.SignedInUser, orgID int64, q Query) *backend.DataResponse {
response := s.doSearchQuery(ctx, q, s.cfg.AppSubURL, orgID)
return response
}
func (s *StandardSearchService) doSearchQuery(ctx context.Context, qry Query, _ string, orgID int64) *backend.DataResponse {
response := &backend.DataResponse{}
// will use stack id for cloud and org id for on-prem
tenantId := request.GetNamespaceMapper(s.cfg)(orgID)
req := &resource.SearchRequest{Tenant: tenantId, Query: qry.Query, Limit: int64(qry.Limit), Offset: int64(qry.From)}
res, err := s.resourceClient.Search(ctx, req)
if err != nil {
s.logger.Error("Failed to search resources", "error", err)
response.Error = err
return response
}
// TODO: implement this correctly
frame := data.NewFrame("results", data.NewField("value", nil, []string{}))
frame.Meta = &data.FrameMeta{Notices: []data.Notice{{Text: "TODO"}}}
for _, r := range res.Items {
frame.AppendRow(string(r.Value))
}
response.Frames = append(response.Frames, frame)
return response
}