diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 4e9c6cc9064..c04f6452dc7 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -53,11 +53,21 @@ type cache struct { expiration *time.Time } +type CloudwatchDatasource struct { + Profile string + Region string + AssumeRoleArn string + Namespace string + + AccessKey string + SecretKey string +} + var awsCredentialCache map[string]cache = make(map[string]cache) var credentialCacheLock sync.RWMutex -func getCredentials(profile string, region string, assumeRoleArn string) *credentials.Credentials { - cacheKey := profile + ":" + assumeRoleArn +func getCredentials(cwDatasource *CloudwatchDatasource) *credentials.Credentials { + cacheKey := cwDatasource.Profile + ":" + cwDatasource.AssumeRoleArn credentialCacheLock.RLock() if _, ok := awsCredentialCache[cacheKey]; ok { if awsCredentialCache[cacheKey].expiration != nil && @@ -74,9 +84,9 @@ func getCredentials(profile string, region string, assumeRoleArn string) *creden sessionToken := "" var expiration *time.Time expiration = nil - if strings.Index(assumeRoleArn, "arn:aws:iam:") == 0 { + if strings.Index(cwDatasource.AssumeRoleArn, "arn:aws:iam:") == 0 { params := &sts.AssumeRoleInput{ - RoleArn: aws.String(assumeRoleArn), + RoleArn: aws.String(cwDatasource.AssumeRoleArn), RoleSessionName: aws.String("GrafanaSession"), DurationSeconds: aws.Int64(900), } @@ -85,13 +95,14 @@ func getCredentials(profile string, region string, assumeRoleArn string) *creden stsCreds := credentials.NewChainCredentials( []credentials.Provider{ &credentials.EnvProvider{}, - &credentials.SharedCredentialsProvider{Filename: "", Profile: profile}, + &credentials.SharedCredentialsProvider{Filename: "", Profile: cwDatasource.Profile}, &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(stsSess), ExpiryWindow: 5 * time.Minute}, }) stsConfig := &aws.Config{ - Region: aws.String(region), + Region: aws.String(cwDatasource.Region), Credentials: stsCreds, } + svc := sts.New(session.New(stsConfig), stsConfig) resp, err := svc.AssumeRole(params) if err != nil { @@ -115,9 +126,14 @@ func getCredentials(profile string, region string, assumeRoleArn string) *creden SessionToken: sessionToken, }}, &credentials.EnvProvider{}, - &credentials.SharedCredentialsProvider{Filename: "", Profile: profile}, + &credentials.StaticProvider{Value: credentials.Value{ + AccessKeyID: cwDatasource.AccessKey, + SecretAccessKey: cwDatasource.SecretKey, + }}, + &credentials.SharedCredentialsProvider{Filename: "", Profile: cwDatasource.Profile}, &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, }) + credentialCacheLock.Lock() awsCredentialCache[cacheKey] = cache{ credential: creds, @@ -130,9 +146,18 @@ func getCredentials(profile string, region string, assumeRoleArn string) *creden func getAwsConfig(req *cwRequest) *aws.Config { assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString() + accessKey := req.DataSource.JsonData.Get("accessKey").MustString() + secretKey := req.DataSource.JsonData.Get("secretKey").MustString() + cfg := &aws.Config{ - Region: aws.String(req.Region), - Credentials: getCredentials(req.DataSource.Database, req.Region, assumeRoleArn), + Region: aws.String(req.Region), + Credentials: getCredentials(&CloudwatchDatasource{ + AccessKey: accessKey, + SecretKey: secretKey, + Region: req.Region, + Profile: req.DataSource.Database, + AssumeRoleArn: assumeRoleArn, + }), } return cfg } diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index 68843ba5774..d8b3ba423c4 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -193,7 +193,19 @@ func handleGetMetrics(req *cwRequest, c *middleware.Context) { } else { var err error assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString() - if namespaceMetrics, err = getMetricsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, assumeRoleArn, getAllMetrics); err != nil { + accessKey := req.DataSource.JsonData.Get("accessKey").MustString() + secretKey := req.DataSource.JsonData.Get("secretKey").MustString() + + cwData := &CloudwatchDatasource{ + AssumeRoleArn: assumeRoleArn, + Region: req.Region, + Namespace: reqParam.Parameters.Namespace, + Profile: req.DataSource.Database, + AccessKey: accessKey, + SecretKey: secretKey, + } + + if namespaceMetrics, err = getMetricsForCustomMetrics(cwData, getAllMetrics); err != nil { c.JsonApiErr(500, "Unable to call AWS API", err) return } @@ -227,7 +239,18 @@ func handleGetDimensions(req *cwRequest, c *middleware.Context) { } else { var err error assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString() - if dimensionValues, err = getDimensionsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, assumeRoleArn, getAllMetrics); err != nil { + accessKey := req.DataSource.JsonData.Get("accessKey").MustString() + secretKey := req.DataSource.JsonData.Get("secretKey").MustString() + + cwDatasource := &CloudwatchDatasource{ + Region: req.Region, + Namespace: reqParam.Parameters.Namespace, + Profile: req.DataSource.Database, + AssumeRoleArn: assumeRoleArn, + AccessKey: accessKey, + SecretKey: secretKey, + } + if dimensionValues, err = getDimensionsForCustomMetrics(cwDatasource, getAllMetrics); err != nil { c.JsonApiErr(500, "Unable to call AWS API", err) return } @@ -242,16 +265,16 @@ func handleGetDimensions(req *cwRequest, c *middleware.Context) { c.JSON(200, result) } -func getAllMetrics(region string, namespace string, database string, assumeRoleArn string) (cloudwatch.ListMetricsOutput, error) { +func getAllMetrics(cwData *CloudwatchDatasource) (cloudwatch.ListMetricsOutput, error) { cfg := &aws.Config{ - Region: aws.String(region), - Credentials: getCredentials(database, region, assumeRoleArn), + Region: aws.String(cwData.Region), + Credentials: getCredentials(cwData), } svc := cloudwatch.New(session.New(cfg), cfg) params := &cloudwatch.ListMetricsInput{ - Namespace: aws.String(namespace), + Namespace: aws.String(cwData.Namespace), } var resp cloudwatch.ListMetricsOutput @@ -272,8 +295,8 @@ func getAllMetrics(region string, namespace string, database string, assumeRoleA var metricsCacheLock sync.Mutex -func getMetricsForCustomMetrics(region string, namespace string, database string, assumeRoleArn string, getAllMetrics func(string, string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) { - result, err := getAllMetrics(region, namespace, database, assumeRoleArn) +func getMetricsForCustomMetrics(cwDatasource *CloudwatchDatasource, getAllMetrics func(*CloudwatchDatasource) (cloudwatch.ListMetricsOutput, error)) ([]string, error) { + result, err := getAllMetrics(cwDatasource) if err != nil { return []string{}, err } @@ -281,37 +304,37 @@ func getMetricsForCustomMetrics(region string, namespace string, database string metricsCacheLock.Lock() defer metricsCacheLock.Unlock() - if _, ok := customMetricsMetricsMap[database]; !ok { - customMetricsMetricsMap[database] = make(map[string]map[string]*CustomMetricsCache) + if _, ok := customMetricsMetricsMap[cwDatasource.Profile]; !ok { + customMetricsMetricsMap[cwDatasource.Profile] = make(map[string]map[string]*CustomMetricsCache) } - if _, ok := customMetricsMetricsMap[database][region]; !ok { - customMetricsMetricsMap[database][region] = make(map[string]*CustomMetricsCache) + if _, ok := customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region]; !ok { + customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region] = make(map[string]*CustomMetricsCache) } - if _, ok := customMetricsMetricsMap[database][region][namespace]; !ok { - customMetricsMetricsMap[database][region][namespace] = &CustomMetricsCache{} - customMetricsMetricsMap[database][region][namespace].Cache = make([]string, 0) + if _, ok := customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace]; !ok { + customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace] = &CustomMetricsCache{} + customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache = make([]string, 0) } - if customMetricsMetricsMap[database][region][namespace].Expire.After(time.Now()) { - return customMetricsMetricsMap[database][region][namespace].Cache, nil + if customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Expire.After(time.Now()) { + return customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache, nil } - customMetricsMetricsMap[database][region][namespace].Cache = make([]string, 0) - customMetricsMetricsMap[database][region][namespace].Expire = time.Now().Add(5 * time.Minute) + customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache = make([]string, 0) + customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Expire = time.Now().Add(5 * time.Minute) for _, metric := range result.Metrics { - if isDuplicate(customMetricsMetricsMap[database][region][namespace].Cache, *metric.MetricName) { + if isDuplicate(customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache, *metric.MetricName) { continue } - customMetricsMetricsMap[database][region][namespace].Cache = append(customMetricsMetricsMap[database][region][namespace].Cache, *metric.MetricName) + customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache = append(customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache, *metric.MetricName) } - return customMetricsMetricsMap[database][region][namespace].Cache, nil + return customMetricsMetricsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache, nil } var dimensionsCacheLock sync.Mutex -func getDimensionsForCustomMetrics(region string, namespace string, database string, assumeRoleArn string, getAllMetrics func(string, string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) { - result, err := getAllMetrics(region, namespace, database, assumeRoleArn) +func getDimensionsForCustomMetrics(cwDatasource *CloudwatchDatasource, getAllMetrics func(*CloudwatchDatasource) (cloudwatch.ListMetricsOutput, error)) ([]string, error) { + result, err := getAllMetrics(cwDatasource) if err != nil { return []string{}, err } @@ -319,33 +342,33 @@ func getDimensionsForCustomMetrics(region string, namespace string, database str dimensionsCacheLock.Lock() defer dimensionsCacheLock.Unlock() - if _, ok := customMetricsDimensionsMap[database]; !ok { - customMetricsDimensionsMap[database] = make(map[string]map[string]*CustomMetricsCache) + if _, ok := customMetricsDimensionsMap[cwDatasource.Profile]; !ok { + customMetricsDimensionsMap[cwDatasource.Profile] = make(map[string]map[string]*CustomMetricsCache) } - if _, ok := customMetricsDimensionsMap[database][region]; !ok { - customMetricsDimensionsMap[database][region] = make(map[string]*CustomMetricsCache) + if _, ok := customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region]; !ok { + customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region] = make(map[string]*CustomMetricsCache) } - if _, ok := customMetricsDimensionsMap[database][region][namespace]; !ok { - customMetricsDimensionsMap[database][region][namespace] = &CustomMetricsCache{} - customMetricsDimensionsMap[database][region][namespace].Cache = make([]string, 0) + if _, ok := customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace]; !ok { + customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace] = &CustomMetricsCache{} + customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache = make([]string, 0) } - if customMetricsDimensionsMap[database][region][namespace].Expire.After(time.Now()) { - return customMetricsDimensionsMap[database][region][namespace].Cache, nil + if customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Expire.After(time.Now()) { + return customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache, nil } - customMetricsDimensionsMap[database][region][namespace].Cache = make([]string, 0) - customMetricsDimensionsMap[database][region][namespace].Expire = time.Now().Add(5 * time.Minute) + customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache = make([]string, 0) + customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Expire = time.Now().Add(5 * time.Minute) for _, metric := range result.Metrics { for _, dimension := range metric.Dimensions { - if isDuplicate(customMetricsDimensionsMap[database][region][namespace].Cache, *dimension.Name) { + if isDuplicate(customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache, *dimension.Name) { continue } - customMetricsDimensionsMap[database][region][namespace].Cache = append(customMetricsDimensionsMap[database][region][namespace].Cache, *dimension.Name) + customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache = append(customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache, *dimension.Name) } } - return customMetricsDimensionsMap[database][region][namespace].Cache, nil + return customMetricsDimensionsMap[cwDatasource.Profile][cwDatasource.Region][cwDatasource.Namespace].Cache, nil } func isDuplicate(nameList []string, target string) bool { diff --git a/public/app/plugins/datasource/cloudwatch/partials/config.html b/public/app/plugins/datasource/cloudwatch/partials/config.html index 89e52e76ace..94c495d8fbf 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/config.html +++ b/public/app/plugins/datasource/cloudwatch/partials/config.html @@ -1,34 +1,48 @@

CloudWatch details

-
- - - - Credentials profile name, as specified in ~/.aws/credentials, leave blank for default - -
-
- -
- - - Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region. - -
-
-
- - - - Namespaces of Custom Metrics - -
-
- - - - ARN of Assume Role - -
+
+ + + + Credentials profile name, as specified in ~/.aws/credentials, leave blank for default + +
+
+ + + + Accesskey + +
+
+ + + + Secret key + +
+
+ +
+ + + Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region. + +
+
+
+ + + + Namespaces of Custom Metrics + +
+
+ + + + ARN of Assume Role + +