diff --git a/pkg/tsdb/cloudmonitoring/cloudmonitoring.go b/pkg/tsdb/cloudmonitoring/cloudmonitoring.go index 4ed67d7d1a2..a319682e766 100644 --- a/pkg/tsdb/cloudmonitoring/cloudmonitoring.go +++ b/pkg/tsdb/cloudmonitoring/cloudmonitoring.go @@ -23,7 +23,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana/pkg/api/pluginproxy" "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" @@ -63,6 +62,8 @@ var ( ) const ( + dsName = "stackdriver" + gceAuthentication string = "gce" jwtAuthentication string = "jwt" metricQueryType string = "metrics" @@ -87,7 +88,7 @@ func ProvideService(cfg *setting.Cfg, httpClientProvider httpclient.Provider, pl QueryDataHandler: s, }) - if err := s.backendPluginManager.Register("stackdriver", factory); err != nil { + if err := s.backendPluginManager.Register(dsName, factory); err != nil { slog.Error("Failed to register plugin", "error", err) } return s @@ -112,9 +113,10 @@ type datasourceInfo struct { url string authenticationType string defaultProject string + clientEmail string + tokenUri string client *http.Client - jsonData map[string]interface{} decryptedSecureJSONData map[string]string } @@ -126,16 +128,6 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst return nil, fmt.Errorf("error reading settings: %w", err) } - opts, err := settings.HTTPClientOptions() - if err != nil { - return nil, err - } - - client, err := httpClientProvider.New(opts) - if err != nil { - return nil, err - } - authType := jwtAuthentication if authTypeOverride, ok := jsonData["authenticationType"].(string); ok && authTypeOverride != "" { authType = authTypeOverride @@ -146,16 +138,38 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst defaultProject = jsonData["defaultProject"].(string) } - return &datasourceInfo{ + var clientEmail string + if jsonData["clientEmail"] != nil { + clientEmail = jsonData["clientEmail"].(string) + } + + var tokenUri string + if jsonData["tokenUri"] != nil { + tokenUri = jsonData["tokenUri"].(string) + } + + dsInfo := &datasourceInfo{ id: settings.ID, updated: settings.Updated, url: settings.URL, authenticationType: authType, defaultProject: defaultProject, - client: client, - jsonData: jsonData, + clientEmail: clientEmail, + tokenUri: tokenUri, decryptedSecureJSONData: settings.DecryptedSecureJSONData, - }, nil + } + + opts, err := settings.HTTPClientOptions() + if err != nil { + return nil, err + } + + dsInfo.client, err = newHTTPClient(dsInfo, opts, httpClientProvider) + if err != nil { + return nil, err + } + + return dsInfo, nil } } @@ -340,14 +354,6 @@ func (s *Service) buildQueryExecutors(req *backend.QueryDataRequest) ([]cloudMon return cloudMonitoringQueryExecutors, nil } -func reverse(s string) string { - chars := []rune(s) - for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 { - chars[i], chars[j] = chars[j], chars[i] - } - return string(chars) -} - func interpolateFilterWildcards(value string) string { matches := strings.Count(value, "*") switch { @@ -478,19 +484,6 @@ func calculateAlignmentPeriod(alignmentPeriod string, intervalMs int64, duration return alignmentPeriod } -func toSnakeCase(str string) string { - return strings.ToLower(matchAllCap.ReplaceAllString(str, "${1}_${2}")) -} - -func containsLabel(labels []string, newLabel string) bool { - for _, val := range labels { - if val == newLabel { - return true - } - } - return false -} - func formatLegendKeys(metricType string, defaultMetricName string, labels map[string]string, additionalLabels map[string]string, query *cloudMonitoringTimeSeriesFilter) string { if query.AliasBy == "" { @@ -589,34 +582,14 @@ func (s *Service) createRequest(ctx context.Context, pluginCtx backend.PluginCon if body != nil { method = http.MethodPost } - req, err := http.NewRequest(method, "https://monitoring.googleapis.com/", body) + req, err := http.NewRequest(method, cloudMonitoringRoute.url, body) if err != nil { slog.Error("Failed to create request", "error", err) return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") - - // find plugin - plugin := s.pluginManager.GetDataSource(pluginCtx.PluginID) - if plugin == nil { - return nil, errors.New("unable to find datasource plugin CloudMonitoring") - } - - var cloudMonitoringRoute *plugins.AppPluginRoute - for _, route := range plugin.Routes { - if route.Path == "cloudmonitoring" { - cloudMonitoringRoute = route - break - } - } - - pluginproxy.ApplyRoute(ctx, req, proxyPass, cloudMonitoringRoute, pluginproxy.DSInfo{ - ID: dsInfo.id, - Updated: dsInfo.updated, - JSONData: dsInfo.jsonData, - DecryptedSecureJSONData: dsInfo.decryptedSecureJSONData, - }, s.cfg) + req.URL.Path = proxyPass return req, nil } diff --git a/pkg/tsdb/cloudmonitoring/httpclient.go b/pkg/tsdb/cloudmonitoring/httpclient.go new file mode 100644 index 00000000000..711795be6dd --- /dev/null +++ b/pkg/tsdb/cloudmonitoring/httpclient.go @@ -0,0 +1,56 @@ +package cloudmonitoring + +import ( + "net/http" + + "github.com/grafana/grafana-google-sdk-go/pkg/tokenprovider" + "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + infrahttp "github.com/grafana/grafana/pkg/infra/httpclient" +) + +var cloudMonitoringRoute = struct { + path string + method string + url string + scopes []string +}{ + path: "cloudmonitoring", + method: "GET", + url: "https://monitoring.googleapis.com", + scopes: []string{"https://www.googleapis.com/auth/monitoring.read"}, +} + +func getMiddleware(model *datasourceInfo) (httpclient.Middleware, error) { + providerConfig := tokenprovider.Config{ + RoutePath: cloudMonitoringRoute.path, + RouteMethod: cloudMonitoringRoute.method, + DataSourceID: model.id, + DataSourceUpdated: model.updated, + Scopes: cloudMonitoringRoute.scopes, + } + + var provider tokenprovider.TokenProvider + switch model.authenticationType { + case gceAuthentication: + provider = tokenprovider.NewGceAccessTokenProvider(providerConfig) + case jwtAuthentication: + providerConfig.JwtTokenConfig = &tokenprovider.JwtTokenConfig{ + Email: model.clientEmail, + URI: model.tokenUri, + PrivateKey: []byte(model.decryptedSecureJSONData["privateKey"]), + } + provider = tokenprovider.NewJwtAccessTokenProvider(providerConfig) + } + + return tokenprovider.AuthMiddleware(provider), nil +} + +func newHTTPClient(model *datasourceInfo, opts httpclient.Options, clientProvider infrahttp.Provider) (*http.Client, error) { + m, err := getMiddleware(model) + if err != nil { + return nil, err + } + + opts.Middlewares = append(opts.Middlewares, m) + return clientProvider.New(opts) +} diff --git a/pkg/tsdb/cloudmonitoring/time_series_filter.go b/pkg/tsdb/cloudmonitoring/time_series_filter.go index f091b56781e..8f80c9c4adf 100644 --- a/pkg/tsdb/cloudmonitoring/time_series_filter.go +++ b/pkg/tsdb/cloudmonitoring/time_series_filter.go @@ -29,7 +29,7 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) run(ctx context.Context slog.Info("No project name set on query, using project name from datasource", "projectName", projectName) } - r, err := s.createRequest(ctx, req.PluginContext, &dsInfo, path.Join("cloudmonitoringv3/projects", projectName, "timeSeries"), nil) + r, err := s.createRequest(ctx, req.PluginContext, &dsInfo, path.Join("/v3/projects", projectName, "timeSeries"), nil) if err != nil { dr.Error = err return dr, cloudMonitoringResponse{}, "", nil diff --git a/pkg/tsdb/cloudmonitoring/time_series_query.go b/pkg/tsdb/cloudmonitoring/time_series_query.go index 645c413889c..b8320e8ffcf 100644 --- a/pkg/tsdb/cloudmonitoring/time_series_query.go +++ b/pkg/tsdb/cloudmonitoring/time_series_query.go @@ -49,7 +49,7 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, r dr.Error = err return dr, cloudMonitoringResponse{}, "", nil } - r, err := s.createRequest(ctx, req.PluginContext, &dsInfo, path.Join("cloudmonitoringv3/projects", projectName, "timeSeries:query"), bytes.NewBuffer(buf)) + r, err := s.createRequest(ctx, req.PluginContext, &dsInfo, path.Join("/v3/projects", projectName, "timeSeries:query"), bytes.NewBuffer(buf)) if err != nil { dr.Error = err return dr, cloudMonitoringResponse{}, "", nil diff --git a/pkg/tsdb/cloudmonitoring/utils.go b/pkg/tsdb/cloudmonitoring/utils.go new file mode 100644 index 00000000000..91b2b6bacd1 --- /dev/null +++ b/pkg/tsdb/cloudmonitoring/utils.go @@ -0,0 +1,26 @@ +package cloudmonitoring + +import ( + "strings" +) + +func reverse(s string) string { + chars := []rune(s) + for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 { + chars[i], chars[j] = chars[j], chars[i] + } + return string(chars) +} + +func toSnakeCase(str string) string { + return strings.ToLower(matchAllCap.ReplaceAllString(str, "${1}_${2}")) +} + +func containsLabel(labels []string, newLabel string) bool { + for _, val := range labels { + if val == newLabel { + return true + } + } + return false +}