package azuremonitor import ( "context" "encoding/json" "fmt" "net/http" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/metrics" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/resourcegraph" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" ) func ProvideService(cfg *setting.Cfg, httpClientProvider *httpclient.Provider, tracer tracing.Tracer) *Service { proxy := &httpServiceProxy{} executors := map[string]azDatasourceExecutor{ azureMonitor: &metrics.AzureMonitorDatasource{Proxy: proxy}, azureLogAnalytics: &loganalytics.AzureLogAnalyticsDatasource{Proxy: proxy}, azureResourceGraph: &resourcegraph.AzureResourceGraphDatasource{Proxy: proxy}, } im := datasource.NewInstanceManager(NewInstanceSettings(cfg, httpClientProvider, executors)) s := &Service{ im: im, executors: executors, tracer: tracer, } s.queryMux = s.newQueryMux() s.resourceHandler = httpadapter.New(s.newResourceMux()) return s } func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { return s.queryMux.QueryData(ctx, req) } func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { return s.resourceHandler.CallResource(ctx, req, sender) } type Service struct { im instancemgmt.InstanceManager executors map[string]azDatasourceExecutor queryMux *datasource.QueryTypeMux resourceHandler backend.CallResourceHandler tracer tracing.Tracer } func getDatasourceService(cfg *setting.Cfg, clientProvider *httpclient.Provider, dsInfo types.DatasourceInfo, routeName string) (types.DatasourceService, error) { route := dsInfo.Routes[routeName] client, err := newHTTPClient(route, dsInfo, cfg, clientProvider) if err != nil { return types.DatasourceService{}, err } return types.DatasourceService{ URL: dsInfo.Routes[routeName].URL, HTTPClient: client, }, nil } func NewInstanceSettings(cfg *setting.Cfg, clientProvider *httpclient.Provider, executors map[string]azDatasourceExecutor) datasource.InstanceFactoryFunc { return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { jsonData, err := simplejson.NewJson(settings.JSONData) if err != nil { return nil, fmt.Errorf("error reading settings: %w", err) } jsonDataObj := map[string]interface{}{} err = json.Unmarshal(settings.JSONData, &jsonDataObj) if err != nil { return nil, fmt.Errorf("error reading settings: %w", err) } azMonitorSettings := types.AzureMonitorSettings{} err = json.Unmarshal(settings.JSONData, &azMonitorSettings) if err != nil { return nil, fmt.Errorf("error reading settings: %w", err) } cloud, err := getAzureCloud(cfg, jsonData) if err != nil { return nil, fmt.Errorf("error getting credentials: %w", err) } credentials, err := getAzureCredentials(cfg, jsonData, settings.DecryptedSecureJSONData) if err != nil { return nil, fmt.Errorf("error getting credentials: %w", err) } model := types.DatasourceInfo{ Cloud: cloud, Credentials: credentials, Settings: azMonitorSettings, JSONData: jsonDataObj, DecryptedSecureJSONData: settings.DecryptedSecureJSONData, DatasourceID: settings.ID, Routes: routes[cloud], Services: map[string]types.DatasourceService{}, } for routeName := range executors { service, err := getDatasourceService(cfg, clientProvider, model, routeName) if err != nil { return nil, err } model.Services[routeName] = service } return model, nil } } type azDatasourceExecutor interface { ExecuteTimeSeriesQuery(ctx context.Context, originalQueries []backend.DataQuery, dsInfo types.DatasourceInfo, client *http.Client, url string, tracer tracing.Tracer) (*backend.QueryDataResponse, error) ResourceRequest(rw http.ResponseWriter, req *http.Request, cli *http.Client) } func (s *Service) getDataSourceFromPluginReq(req *backend.QueryDataRequest) (types.DatasourceInfo, error) { i, err := s.im.Get(req.PluginContext) if err != nil { return types.DatasourceInfo{}, err } dsInfo, ok := i.(types.DatasourceInfo) if !ok { return types.DatasourceInfo{}, fmt.Errorf("unable to convert datasource from service instance") } dsInfo.OrgID = req.PluginContext.OrgID return dsInfo, nil } func (s *Service) newQueryMux() *datasource.QueryTypeMux { mux := datasource.NewQueryTypeMux() for dsType := range s.executors { // Make a copy of the string to keep the reference after the iterator dst := dsType mux.HandleFunc(dsType, func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { executor := s.executors[dst] dsInfo, err := s.getDataSourceFromPluginReq(req) if err != nil { return nil, err } service, ok := dsInfo.Services[dst] if !ok { return nil, fmt.Errorf("missing service for %s", dst) } return executor.ExecuteTimeSeriesQuery(ctx, req.Queries, dsInfo, service.HTTPClient, service.URL, s.tracer) }) } return mux }