diff --git a/pkg/tsdb/cloudwatch/annotation_query_test.go b/pkg/tsdb/cloudwatch/annotation_query_test.go index da58d7bf35b..5185ce2c889 100644 --- a/pkg/tsdb/cloudwatch/annotation_query_test.go +++ b/pkg/tsdb/cloudwatch/annotation_query_test.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -31,7 +32,7 @@ func TestQuery_AnnotationQuery(t *testing.T) { t.Run("DescribeAlarmsForMetric is called with minimum parameters", func(t *testing.T) { client = fakeCWAnnotationsClient{describeAlarmsForMetricOutput: &cloudwatch.DescribeAlarmsForMetricOutput{}} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -65,7 +66,7 @@ func TestQuery_AnnotationQuery(t *testing.T) { t.Run("DescribeAlarms is called when prefixMatching is true", func(t *testing.T) { client = fakeCWAnnotationsClient{describeAlarmsOutput: &cloudwatch.DescribeAlarmsOutput{}} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index 409e994f6ab..0369a078569 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -34,23 +34,6 @@ import ( "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" ) -type datasourceInfo struct { - profile string - region string - authType awsds.AuthType - assumeRoleARN string - externalID string - namespace string - endpoint string - - accessKey string - secretKey string - - datasourceID int64 - - HTTPClient *http.Client -} - type DataQueryJson struct { QueryType string `json:"type,omitempty"` QueryMode string @@ -65,6 +48,11 @@ type DataQueryJson struct { AlarmNamePrefix string } +type DataSource struct { + Settings *models.CloudWatchSettings + HTTPClient *http.Client +} + const ( cloudWatchTSFormat = "2006-01-02 15:04:05.000" defaultRegion = "default" @@ -120,11 +108,11 @@ func newExecutor(im instancemgmt.InstanceManager, cfg *setting.Cfg, sessions Ses func (e *cloudWatchExecutor) getClients(pluginCtx backend.PluginContext, region string) (models.Clients, error) { r := region if region == defaultRegion { - dsInfo, err := e.getDSInfo(pluginCtx) + instance, err := e.getInstance(pluginCtx) if err != nil { return models.Clients{}, err } - r = dsInfo.region + r = instance.Settings.Region } sess, err := e.newSession(pluginCtx, r) @@ -138,17 +126,7 @@ func (e *cloudWatchExecutor) getClients(pluginCtx backend.PluginContext, region func NewInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc { return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - jsonData := struct { - Profile string `json:"profile"` - Region string `json:"defaultRegion"` - AssumeRoleARN string `json:"assumeRoleArn"` - ExternalID string `json:"externalId"` - Endpoint string `json:"endpoint"` - Namespace string `json:"customMetricsNamespaces"` - AuthType string `json:"authType"` - }{} - - err := json.Unmarshal(settings.JSONData, &jsonData) + instanceSettings, err := models.LoadCloudWatchSettings(settings) if err != nil { return nil, fmt.Errorf("error reading settings: %w", err) } @@ -158,44 +136,10 @@ func NewInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst return nil, fmt.Errorf("error creating http client: %w", err) } - model := datasourceInfo{ - profile: jsonData.Profile, - region: jsonData.Region, - assumeRoleARN: jsonData.AssumeRoleARN, - externalID: jsonData.ExternalID, - endpoint: jsonData.Endpoint, - namespace: jsonData.Namespace, - datasourceID: settings.ID, - HTTPClient: httpClient, - } - - at := awsds.AuthTypeDefault - switch jsonData.AuthType { - case "credentials": - at = awsds.AuthTypeSharedCreds - case "keys": - at = awsds.AuthTypeKeys - case "default": - at = awsds.AuthTypeDefault - case "ec2_iam_role": - at = awsds.AuthTypeEC2IAMRole - case "arn": - at = awsds.AuthTypeDefault - cwlog.Warn("Authentication type \"arn\" is deprecated, falling back to default") - default: - cwlog.Warn("Unrecognized AWS authentication type", "type", jsonData.AuthType) - } - - model.authType = at - - if model.profile == "" { - model.profile = settings.Database // legacy support - } - - model.accessKey = settings.DecryptedSecureJSONData["accessKey"] - model.secretKey = settings.DecryptedSecureJSONData["secretKey"] - - return model, nil + return DataSource{ + Settings: instanceSettings, + HTTPClient: httpClient, + }, nil } } @@ -257,28 +201,28 @@ func (e *cloudWatchExecutor) CheckHealth(ctx context.Context, req *backend.Check } func (e *cloudWatchExecutor) newSession(pluginCtx backend.PluginContext, region string) (*session.Session, error) { - dsInfo, err := e.getDSInfo(pluginCtx) + instance, err := e.getInstance(pluginCtx) if err != nil { return nil, err } if region == defaultRegion { - region = dsInfo.region + region = instance.Settings.Region } return e.sessions.GetSession(awsds.SessionConfig{ // https://github.com/grafana/grafana/issues/46365 // HTTPClient: dsInfo.HTTPClient, Settings: awsds.AWSDatasourceSettings{ - Profile: dsInfo.profile, + Profile: instance.Settings.Profile, Region: region, - AuthType: dsInfo.authType, - AssumeRoleARN: dsInfo.assumeRoleARN, - ExternalID: dsInfo.externalID, - Endpoint: dsInfo.endpoint, - DefaultRegion: dsInfo.region, - AccessKey: dsInfo.accessKey, - SecretKey: dsInfo.secretKey, + AuthType: instance.Settings.AuthType, + AssumeRoleARN: instance.Settings.AssumeRoleARN, + ExternalID: instance.Settings.ExternalID, + Endpoint: instance.Settings.Endpoint, + DefaultRegion: instance.Settings.Region, + AccessKey: instance.Settings.AccessKey, + SecretKey: instance.Settings.SecretKey, }, UserAgentName: aws.String("Cloudwatch"), }) @@ -407,11 +351,11 @@ func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, req *back region := model.Region if model.Region == "" || region == defaultRegion { - dsInfo, err := e.getDSInfo(req.PluginContext) + instance, err := e.getInstance(req.PluginContext) if err != nil { return nil, err } - model.Region = dsInfo.region + model.Region = instance.Settings.Region } logsClient, err := e.getCWLogsClient(req.PluginContext, region) @@ -447,13 +391,13 @@ func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, req *back return resp, nil } -func (e *cloudWatchExecutor) getDSInfo(pluginCtx backend.PluginContext) (*datasourceInfo, error) { +func (e *cloudWatchExecutor) getInstance(pluginCtx backend.PluginContext) (*DataSource, error) { i, err := e.im.Get(pluginCtx) if err != nil { return nil, err } - instance := i.(datasourceInfo) + instance := i.(DataSource) return &instance, nil } diff --git a/pkg/tsdb/cloudwatch/cloudwatch_test.go b/pkg/tsdb/cloudwatch/cloudwatch_test.go index d4712711860..32d6c9b9f01 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch_test.go +++ b/pkg/tsdb/cloudwatch/cloudwatch_test.go @@ -32,7 +32,7 @@ func TestNewInstanceSettings(t *testing.T) { tests := []struct { name string settings backend.DataSourceInstanceSettings - expectedDS datasourceInfo + expectedDS DataSource Err require.ErrorAssertionFunc }{ { @@ -52,16 +52,20 @@ func TestNewInstanceSettings(t *testing.T) { "secretKey": "secret", }, }, - expectedDS: datasourceInfo{ - profile: "foo", - region: "us-east2", - assumeRoleARN: "role", - externalID: "id", - endpoint: "bar", - namespace: "ns", - authType: awsds.AuthTypeKeys, - accessKey: "A123", - secretKey: "secret", + expectedDS: DataSource{ + Settings: &models.CloudWatchSettings{ + AWSDatasourceSettings: awsds.AWSDatasourceSettings{ + Profile: "foo", + Region: "us-east2", + AssumeRoleARN: "role", + ExternalID: "id", + Endpoint: "bar", + AuthType: awsds.AuthTypeKeys, + AccessKey: "A123", + SecretKey: "secret", + }, + Namespace: "ns", + }, }, Err: require.NoError, }, @@ -72,19 +76,18 @@ func TestNewInstanceSettings(t *testing.T) { f := NewInstanceSettings(httpclient.NewProvider()) model, err := f(tt.settings) tt.Err(t, err) - datasourceComparer := cmp.Comparer(func(d1 datasourceInfo, d2 datasourceInfo) bool { - return d1.profile == d2.profile && - d1.region == d2.region && - d1.authType == d2.authType && - d1.assumeRoleARN == d2.assumeRoleARN && - d1.externalID == d2.externalID && - d1.namespace == d2.namespace && - d1.endpoint == d2.endpoint && - d1.accessKey == d2.accessKey && - d1.secretKey == d2.secretKey && - d1.datasourceID == d2.datasourceID + datasourceComparer := cmp.Comparer(func(d1 DataSource, d2 DataSource) bool { + return d1.Settings.Profile == d2.Settings.Profile && + d1.Settings.Region == d2.Settings.Region && + d1.Settings.AuthType == d2.Settings.AuthType && + d1.Settings.AssumeRoleARN == d2.Settings.AssumeRoleARN && + d1.Settings.ExternalID == d2.Settings.ExternalID && + d1.Settings.Namespace == d2.Settings.Namespace && + d1.Settings.Endpoint == d2.Settings.Endpoint && + d1.Settings.AccessKey == d2.Settings.AccessKey && + d1.Settings.SecretKey == d2.Settings.SecretKey }) - if !cmp.Equal(model.(datasourceInfo), tt.expectedDS, datasourceComparer) { + if !cmp.Equal(model.(DataSource), tt.expectedDS, datasourceComparer) { t.Errorf("Unexpected result. Expecting\n%v \nGot:\n%v", model, tt.expectedDS) } }) @@ -110,7 +113,7 @@ func Test_CheckHealth(t *testing.T) { t.Run("successfully query metrics and logs", func(t *testing.T) { client = fakeCheckHealthClient{} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -131,7 +134,7 @@ func Test_CheckHealth(t *testing.T) { return nil, fmt.Errorf("some logs query error") }} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -152,7 +155,7 @@ func Test_CheckHealth(t *testing.T) { return fmt.Errorf("some list metrics error") }} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -170,7 +173,7 @@ func Test_CheckHealth(t *testing.T) { t.Run("fail to get clients", func(t *testing.T) { client = fakeCheckHealthClient{} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{getSession: func(c awsds.SessionConfig) (*session.Session, error) { return nil, fmt.Errorf("some sessions error") @@ -201,7 +204,7 @@ func Test_executeLogAlertQuery(t *testing.T) { t.Run("getCWLogsClient is called with region from input JSON", func(t *testing.T) { cli = fakeCWLogsClient{queryResults: cloudwatchlogs.GetQueryResultsOutput{Status: aws.String("Complete")}} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) sess := fakeSessionCache{} executor := newExecutor(im, newTestConfig(), &sess, featuremgmt.WithFeatures()) @@ -227,7 +230,7 @@ func Test_executeLogAlertQuery(t *testing.T) { t.Run("getCWLogsClient is called with region from instance manager when region is default", func(t *testing.T) { cli = fakeCWLogsClient{queryResults: cloudwatchlogs.GetQueryResultsOutput{Status: aws.String("Complete")}} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{region: "instance manager's region"}, nil + return DataSource{Settings: &models.CloudWatchSettings{AWSDatasourceSettings: awsds.AWSDatasourceSettings{Region: "instance manager's region"}}}, nil }) sess := fakeSessionCache{} @@ -264,7 +267,7 @@ func TestQuery_ResourceRequest_DescribeAllLogGroups(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -343,7 +346,7 @@ func TestQuery_ResourceRequest_DescribeLogGroups(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -398,7 +401,7 @@ func TestQuery_ResourceRequest_DescribeLogGroups(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -436,7 +439,7 @@ func TestQuery_ResourceRequest_DescribeLogGroups(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -477,7 +480,7 @@ func Test_CloudWatch_CallResource_Integration_Test(t *testing.T) { return &api } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) t.Run("Should handle dimension value request and return values from the api", func(t *testing.T) { diff --git a/pkg/tsdb/cloudwatch/log_actions.go b/pkg/tsdb/cloudwatch/log_actions.go index 54994b148c1..c0b654f299e 100644 --- a/pkg/tsdb/cloudwatch/log_actions.go +++ b/pkg/tsdb/cloudwatch/log_actions.go @@ -110,12 +110,12 @@ func (e *cloudWatchExecutor) executeLogActions(ctx context.Context, req *backend } func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, model LogQueryJson, query backend.DataQuery, pluginCtx backend.PluginContext) (*data.Frame, error) { - dsInfo, err := e.getDSInfo(pluginCtx) + instance, err := e.getInstance(pluginCtx) if err != nil { return nil, err } - region := dsInfo.region + region := instance.Settings.Region if model.Region != "" { region = model.Region } diff --git a/pkg/tsdb/cloudwatch/log_actions_test.go b/pkg/tsdb/cloudwatch/log_actions_test.go index fdd05b13d1f..950df2f89f5 100644 --- a/pkg/tsdb/cloudwatch/log_actions_test.go +++ b/pkg/tsdb/cloudwatch/log_actions_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -82,7 +83,7 @@ func TestQuery_GetLogEvents(t *testing.T) { cli = fakeCWLogsClient{} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -140,7 +141,7 @@ func TestQuery_GetLogGroupFields(t *testing.T) { const refID = "A" im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -221,7 +222,7 @@ func TestQuery_StartQuery(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -274,7 +275,7 @@ func TestQuery_StartQuery(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -332,7 +333,7 @@ func Test_executeStartQuery(t *testing.T) { t.Run("successfully parses information from JSON to StartQueryWithContext", func(t *testing.T) { cli = fakeCWLogsClient{} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -368,7 +369,7 @@ func Test_executeStartQuery(t *testing.T) { t.Run("does not populate StartQueryInput.limit when no limit provided", func(t *testing.T) { cli = fakeCWLogsClient{} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -424,7 +425,7 @@ func TestQuery_StopQuery(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) timeRange := backend.TimeRange{ @@ -519,7 +520,7 @@ func TestQuery_GetQueryResults(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index eccc3b7650d..4ed640e7fd8 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -55,12 +55,12 @@ func parseMultiSelectValue(input string) []string { // Whenever this list is updated, the frontend list should also be updated. // Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html func (e *cloudWatchExecutor) handleGetRegions(pluginCtx backend.PluginContext, parameters url.Values) ([]suggestData, error) { - dsInfo, err := e.getDSInfo(pluginCtx) + instance, err := e.getInstance(pluginCtx) if err != nil { return nil, err } - profile := dsInfo.profile + profile := instance.Settings.Profile if cache, ok := regionCache.Load(profile); ok { if cache2, ok2 := cache.([]suggestData); ok2 { return cache2, nil @@ -109,12 +109,12 @@ func (e *cloudWatchExecutor) handleGetNamespaces(pluginCtx backend.PluginContext keys = append(keys, key) } - dsInfo, err := e.getDSInfo(pluginCtx) + instance, err := e.getInstance(pluginCtx) if err != nil { return nil, err } - customNamespaces := dsInfo.namespace + customNamespaces := instance.Settings.Namespace if customNamespaces != "" { keys = append(keys, strings.Split(customNamespaces, ",")...) } @@ -395,24 +395,24 @@ func (e *cloudWatchExecutor) getMetricsForCustomMetrics(region, namespace string metricsCacheLock.Lock() defer metricsCacheLock.Unlock() - dsInfo, err := e.getDSInfo(pluginCtx) + instance, err := e.getInstance(pluginCtx) if err != nil { return nil, err } - if _, ok := customMetricsMetricsMap[dsInfo.profile]; !ok { - customMetricsMetricsMap[dsInfo.profile] = make(map[string]map[string]*customMetricsCache) + if _, ok := customMetricsMetricsMap[instance.Settings.Profile]; !ok { + customMetricsMetricsMap[instance.Settings.Profile] = make(map[string]map[string]*customMetricsCache) } - if _, ok := customMetricsMetricsMap[dsInfo.profile][dsInfo.region]; !ok { - customMetricsMetricsMap[dsInfo.profile][dsInfo.region] = make(map[string]*customMetricsCache) + if _, ok := customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region]; !ok { + customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region] = make(map[string]*customMetricsCache) } - if _, ok := customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace]; !ok { - customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace] = &customMetricsCache{} - customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace].Cache = make([]string, 0) + if _, ok := customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace]; !ok { + customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace] = &customMetricsCache{} + customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace].Cache = make([]string, 0) } - if customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace].Expire.After(time.Now()) { - return customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace].Cache, nil + if customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace].Expire.After(time.Now()) { + return customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace].Cache, nil } metrics, err := e.listMetrics(pluginCtx, region, &cloudwatch.ListMetricsInput{ Namespace: aws.String(namespace), @@ -421,18 +421,18 @@ func (e *cloudWatchExecutor) getMetricsForCustomMetrics(region, namespace string return []string{}, err } - customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace].Cache = make([]string, 0) - customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace].Expire = time.Now().Add(5 * time.Minute) + customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace].Cache = make([]string, 0) + customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace].Expire = time.Now().Add(5 * time.Minute) for _, metric := range metrics { - if isDuplicate(customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace].Cache, *metric.MetricName) { + if isDuplicate(customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace].Cache, *metric.MetricName) { continue } - customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace].Cache = append( - customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace].Cache, *metric.MetricName) + customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace].Cache = append( + customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace].Cache, *metric.MetricName) } - return customMetricsMetricsMap[dsInfo.profile][dsInfo.region][namespace].Cache, nil + return customMetricsMetricsMap[instance.Settings.Profile][instance.Settings.Region][namespace].Cache, nil } func (e *cloudWatchExecutor) handleGetLogGroups(pluginCtx backend.PluginContext, parameters url.Values) ([]suggestData, error) { diff --git a/pkg/tsdb/cloudwatch/metric_find_query_test.go b/pkg/tsdb/cloudwatch/metric_find_query_test.go index c618ebed9a5..f20389e169e 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query_test.go +++ b/pkg/tsdb/cloudwatch/metric_find_query_test.go @@ -21,6 +21,7 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/constants" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -52,7 +53,7 @@ func TestQuery_Metrics(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -92,7 +93,7 @@ func TestQuery_Regions(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -159,7 +160,7 @@ func TestQuery_InstanceAttributes(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) filterMap := map[string][]string{ @@ -242,7 +243,7 @@ func TestQuery_EBSVolumeIDs(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -302,7 +303,7 @@ func TestQuery_ResourceARNs(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) tagMap := map[string][]string{ @@ -338,7 +339,7 @@ func TestQuery_ResourceARNs(t *testing.T) { func TestQuery_GetAllMetrics(t *testing.T) { t.Run("all metrics in all namespaces are being returned", func(t *testing.T) { im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) diff --git a/pkg/tsdb/cloudwatch/models/settings.go b/pkg/tsdb/cloudwatch/models/settings.go new file mode 100644 index 00000000000..17b746c95d2 --- /dev/null +++ b/pkg/tsdb/cloudwatch/models/settings.go @@ -0,0 +1,36 @@ +package models + +import ( + "encoding/json" + "fmt" + + "github.com/grafana/grafana-aws-sdk/pkg/awsds" + "github.com/grafana/grafana-plugin-sdk-go/backend" +) + +type CloudWatchSettings struct { + awsds.AWSDatasourceSettings + Namespace string `json:"customMetricsNamespaces"` +} + +func LoadCloudWatchSettings(config backend.DataSourceInstanceSettings) (*CloudWatchSettings, error) { + instance := &CloudWatchSettings{} + if config.JSONData != nil && len(config.JSONData) > 1 { + if err := json.Unmarshal(config.JSONData, instance); err != nil { + return nil, fmt.Errorf("could not unmarshal DatasourceSettings json: %w", err) + } + } + + if instance.Region == "default" || instance.Region == "" { + instance.Region = instance.DefaultRegion + } + + if instance.Profile == "" { + instance.Profile = config.Database + } + + instance.AccessKey = config.DecryptedSecureJSONData["accessKey"] + instance.SecretKey = config.DecryptedSecureJSONData["secretKey"] + + return instance, nil +} diff --git a/pkg/tsdb/cloudwatch/models/settings_test.go b/pkg/tsdb/cloudwatch/models/settings_test.go new file mode 100644 index 00000000000..55ea1ddac59 --- /dev/null +++ b/pkg/tsdb/cloudwatch/models/settings_test.go @@ -0,0 +1,74 @@ +package models + +import ( + "testing" + + "github.com/grafana/grafana-aws-sdk/pkg/awsds" + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Settings_LoadCloudWatchSettings(t *testing.T) { + t.Run("Should parse keys query type", func(t *testing.T) { + settings := backend.DataSourceInstanceSettings{ + ID: 33, + JSONData: []byte(`{ + "authType": "keys", + "assumeRoleArn": "arn:aws:iam::123456789012:role/grafana", + "customMetricsNamespaces": "AWS/EC2,AWS/ELB", + "defaultRegion": "us-east-1", + "externalId": "123456789012", + "profile": "default", + "endpoint": "https://monitoring.us-east-1.amazonaws.com" + }`), + DecryptedSecureJSONData: map[string]string{ + "accessKey": "AKIAIOSFODNN7EXAMPLE", + "secretKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + }, + } + + s, err := LoadCloudWatchSettings(settings) + require.NoError(t, err) + assert.Equal(t, awsds.AuthTypeKeys, s.AuthType) + assert.Equal(t, "arn:aws:iam::123456789012:role/grafana", s.AssumeRoleARN) + assert.Equal(t, "AWS/EC2,AWS/ELB", s.Namespace) + assert.Equal(t, "us-east-1", s.Region) + assert.Equal(t, "123456789012", s.ExternalID) + assert.Equal(t, "default", s.Profile) + assert.Equal(t, "https://monitoring.us-east-1.amazonaws.com", s.Endpoint) + assert.Equal(t, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", s.SecretKey) + assert.Equal(t, "AKIAIOSFODNN7EXAMPLE", s.AccessKey) + }) + + t.Run("Should handle legacy auth type arn as default", func(t *testing.T) { + settings := backend.DataSourceInstanceSettings{ + ID: 33, + JSONData: []byte(`{ + "authType": "arn", + "assumeRoleArn": "arn:aws:iam::123456789012:role/grafana", + "customMetricsNamespaces": "AWS/EC2,AWS/ELB", + "defaultRegion": "us-east-1", + "externalId": "123456789012", + "profile": "default", + "endpoint": "https://monitoring.us-east-1.amazonaws.com" + }`), + DecryptedSecureJSONData: map[string]string{ + "accessKey": "AKIAIOSFODNN7EXAMPLE", + "secretKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + }, + } + + s, err := LoadCloudWatchSettings(settings) + require.NoError(t, err) + assert.Equal(t, awsds.AuthTypeDefault, s.AuthType) + assert.Equal(t, "arn:aws:iam::123456789012:role/grafana", s.AssumeRoleARN) + assert.Equal(t, "AWS/EC2,AWS/ELB", s.Namespace) + assert.Equal(t, "us-east-1", s.Region) + assert.Equal(t, "123456789012", s.ExternalID) + assert.Equal(t, "default", s.Profile) + assert.Equal(t, "https://monitoring.us-east-1.amazonaws.com", s.Endpoint) + assert.Equal(t, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", s.SecretKey) + assert.Equal(t, "AKIAIOSFODNN7EXAMPLE", s.AccessKey) + }) +} diff --git a/pkg/tsdb/cloudwatch/time_series_query_test.go b/pkg/tsdb/cloudwatch/time_series_query_test.go index 690cb461c4c..1ceff736b2e 100644 --- a/pkg/tsdb/cloudwatch/time_series_query_test.go +++ b/pkg/tsdb/cloudwatch/time_series_query_test.go @@ -56,7 +56,7 @@ func TestTimeSeriesQuery(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) @@ -156,7 +156,7 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) t.Run("Queries with the same region should call GetSession with that region 1 time and call GetMetricDataWithContext 1 time", func(t *testing.T) { @@ -348,7 +348,7 @@ func Test_QueryData_timeSeriesQuery_GetMetricDataWithContext(t *testing.T) { } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) t.Run("passes query label as GetMetricData label when dynamic labels feature toggle is enabled", func(t *testing.T) { @@ -446,7 +446,7 @@ func Test_QueryData_response_data_frame_names(t *testing.T) { }, } im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil + return DataSource{Settings: &models.CloudWatchSettings{}}, nil }) executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures())