diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go
index 4e9c6cc9064..991d336411c 100644
--- a/pkg/api/cloudwatch/cloudwatch.go
+++ b/pkg/api/cloudwatch/cloudwatch.go
@@ -33,6 +33,39 @@ type cwRequest struct {
DataSource *m.DataSource
}
+type datasourceInfo struct {
+ Profile string
+ Region string
+ AssumeRoleArn string
+ Namespace string
+
+ AccessKey string
+ SecretKey string
+}
+
+func (req *cwRequest) GetDatasourceInfo() *datasourceInfo {
+ assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
+ accessKey := ""
+ secretKey := ""
+
+ for key, value := range req.DataSource.SecureJsonData.Decrypt() {
+ if key == "accessKey" {
+ accessKey = value
+ }
+ if key == "secretKey" {
+ secretKey = value
+ }
+ }
+
+ return &datasourceInfo{
+ AssumeRoleArn: assumeRoleArn,
+ Region: req.Region,
+ Profile: req.DataSource.Database,
+ AccessKey: accessKey,
+ SecretKey: secretKey,
+ }
+}
+
func init() {
actionHandlers = map[string]actionHandler{
"GetMetricStatistics": handleGetMetricStatistics,
@@ -56,8 +89,8 @@ type cache struct {
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(dsInfo *datasourceInfo) *credentials.Credentials {
+ cacheKey := dsInfo.Profile + ":" + dsInfo.AssumeRoleArn
credentialCacheLock.RLock()
if _, ok := awsCredentialCache[cacheKey]; ok {
if awsCredentialCache[cacheKey].expiration != nil &&
@@ -74,9 +107,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(dsInfo.AssumeRoleArn, "arn:aws:iam:") == 0 {
params := &sts.AssumeRoleInput{
- RoleArn: aws.String(assumeRoleArn),
+ RoleArn: aws.String(dsInfo.AssumeRoleArn),
RoleSessionName: aws.String("GrafanaSession"),
DurationSeconds: aws.Int64(900),
}
@@ -85,13 +118,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: dsInfo.Profile},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(stsSess), ExpiryWindow: 5 * time.Minute},
})
stsConfig := &aws.Config{
- Region: aws.String(region),
+ Region: aws.String(dsInfo.Region),
Credentials: stsCreds,
}
+
svc := sts.New(session.New(stsConfig), stsConfig)
resp, err := svc.AssumeRole(params)
if err != nil {
@@ -115,9 +149,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: dsInfo.AccessKey,
+ SecretAccessKey: dsInfo.SecretKey,
+ }},
+ &credentials.SharedCredentialsProvider{Filename: "", Profile: dsInfo.Profile},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
})
+
credentialCacheLock.Lock()
awsCredentialCache[cacheKey] = cache{
credential: creds,
@@ -129,10 +168,9 @@ func getCredentials(profile string, region string, assumeRoleArn string) *creden
}
func getAwsConfig(req *cwRequest) *aws.Config {
- assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
cfg := &aws.Config{
Region: aws.String(req.Region),
- Credentials: getCredentials(req.DataSource.Database, req.Region, assumeRoleArn),
+ Credentials: getCredentials(req.GetDatasourceInfo()),
}
return cfg
}
diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go
index 68843ba5774..1880c6375b0 100644
--- a/pkg/api/cloudwatch/metrics.go
+++ b/pkg/api/cloudwatch/metrics.go
@@ -192,8 +192,10 @@ 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 {
+ cwData := req.GetDatasourceInfo()
+ cwData.Namespace = reqParam.Parameters.Namespace
+
+ if namespaceMetrics, err = getMetricsForCustomMetrics(cwData, getAllMetrics); err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
@@ -226,8 +228,10 @@ 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 {
+ dsInfo := req.GetDatasourceInfo()
+ dsInfo.Namespace = reqParam.Parameters.Namespace
+
+ if dimensionValues, err = getDimensionsForCustomMetrics(dsInfo, getAllMetrics); err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
@@ -242,16 +246,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 *datasourceInfo) (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 +276,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(dsInfo *datasourceInfo, getAllMetrics func(*datasourceInfo) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
+ result, err := getAllMetrics(dsInfo)
if err != nil {
return []string{}, err
}
@@ -281,37 +285,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[dsInfo.Profile]; !ok {
+ customMetricsMetricsMap[dsInfo.Profile] = make(map[string]map[string]*CustomMetricsCache)
}
- if _, ok := customMetricsMetricsMap[database][region]; !ok {
- customMetricsMetricsMap[database][region] = make(map[string]*CustomMetricsCache)
+ if _, ok := customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region]; !ok {
+ customMetricsMetricsMap[dsInfo.Profile][dsInfo.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[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace]; !ok {
+ customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace] = &CustomMetricsCache{}
+ customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
}
- if customMetricsMetricsMap[database][region][namespace].Expire.After(time.Now()) {
- return customMetricsMetricsMap[database][region][namespace].Cache, nil
+ if customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire.After(time.Now()) {
+ return customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, nil
}
- customMetricsMetricsMap[database][region][namespace].Cache = make([]string, 0)
- customMetricsMetricsMap[database][region][namespace].Expire = time.Now().Add(5 * time.Minute)
+ customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
+ customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.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[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, *metric.MetricName) {
continue
}
- customMetricsMetricsMap[database][region][namespace].Cache = append(customMetricsMetricsMap[database][region][namespace].Cache, *metric.MetricName)
+ customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = append(customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, *metric.MetricName)
}
- return customMetricsMetricsMap[database][region][namespace].Cache, nil
+ return customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.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(dsInfo *datasourceInfo, getAllMetrics func(*datasourceInfo) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
+ result, err := getAllMetrics(dsInfo)
if err != nil {
return []string{}, err
}
@@ -319,33 +323,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[dsInfo.Profile]; !ok {
+ customMetricsDimensionsMap[dsInfo.Profile] = make(map[string]map[string]*CustomMetricsCache)
}
- if _, ok := customMetricsDimensionsMap[database][region]; !ok {
- customMetricsDimensionsMap[database][region] = make(map[string]*CustomMetricsCache)
+ if _, ok := customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region]; !ok {
+ customMetricsDimensionsMap[dsInfo.Profile][dsInfo.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[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace]; !ok {
+ customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace] = &CustomMetricsCache{}
+ customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
}
- if customMetricsDimensionsMap[database][region][namespace].Expire.After(time.Now()) {
- return customMetricsDimensionsMap[database][region][namespace].Cache, nil
+ if customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire.After(time.Now()) {
+ return customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, nil
}
- customMetricsDimensionsMap[database][region][namespace].Cache = make([]string, 0)
- customMetricsDimensionsMap[database][region][namespace].Expire = time.Now().Add(5 * time.Minute)
+ customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
+ customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.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[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, *dimension.Name) {
continue
}
- customMetricsDimensionsMap[database][region][namespace].Cache = append(customMetricsDimensionsMap[database][region][namespace].Cache, *dimension.Name)
+ customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = append(customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, *dimension.Name)
}
}
- return customMetricsDimensionsMap[database][region][namespace].Cache, nil
+ return customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, nil
}
func isDuplicate(nameList []string, target string) bool {
diff --git a/pkg/api/cloudwatch/metrics_test.go b/pkg/api/cloudwatch/metrics_test.go
index 6dd68037613..4ac8a70a273 100644
--- a/pkg/api/cloudwatch/metrics_test.go
+++ b/pkg/api/cloudwatch/metrics_test.go
@@ -11,11 +11,13 @@ import (
func TestCloudWatchMetrics(t *testing.T) {
Convey("When calling getMetricsForCustomMetrics", t, func() {
- region := "us-east-1"
- namespace := "Foo"
- database := "default"
- assumeRoleArn := ""
- f := func(region string, namespace string, database string, assumeRoleArn string) (cloudwatch.ListMetricsOutput, error) {
+ dsInfo := &datasourceInfo{
+ Region: "us-east-1",
+ Namespace: "Foo",
+ Profile: "default",
+ AssumeRoleArn: "",
+ }
+ f := func(dsInfo *datasourceInfo) (cloudwatch.ListMetricsOutput, error) {
return cloudwatch.ListMetricsOutput{
Metrics: []*cloudwatch.Metric{
{
@@ -29,7 +31,7 @@ func TestCloudWatchMetrics(t *testing.T) {
},
}, nil
}
- metrics, _ := getMetricsForCustomMetrics(region, namespace, database, assumeRoleArn, f)
+ metrics, _ := getMetricsForCustomMetrics(dsInfo, f)
Convey("Should contain Test_MetricName", func() {
So(metrics, ShouldContain, "Test_MetricName")
@@ -37,11 +39,13 @@ func TestCloudWatchMetrics(t *testing.T) {
})
Convey("When calling getDimensionsForCustomMetrics", t, func() {
- region := "us-east-1"
- namespace := "Foo"
- database := "default"
- assumeRoleArn := ""
- f := func(region string, namespace string, database string, assumeRoleArn string) (cloudwatch.ListMetricsOutput, error) {
+ dsInfo := &datasourceInfo{
+ Region: "us-east-1",
+ Namespace: "Foo",
+ Profile: "default",
+ AssumeRoleArn: "",
+ }
+ f := func(dsInfo *datasourceInfo) (cloudwatch.ListMetricsOutput, error) {
return cloudwatch.ListMetricsOutput{
Metrics: []*cloudwatch.Metric{
{
@@ -55,7 +59,7 @@ func TestCloudWatchMetrics(t *testing.T) {
},
}, nil
}
- dimensionKeys, _ := getDimensionsForCustomMetrics(region, namespace, database, assumeRoleArn, f)
+ dimensionKeys, _ := getDimensionsForCustomMetrics(dsInfo, f)
Convey("Should contain Test_DimensionName", func() {
So(dimensionKeys, ShouldContain, "Test_DimensionName")
diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go
index 87c743c9ce6..cc305221817 100644
--- a/pkg/api/datasources.go
+++ b/pkg/api/datasources.go
@@ -215,5 +215,11 @@ func convertModelToDtos(ds *m.DataSource) dtos.DataSource {
dto.TLSAuth.ClientKeySet = len(ds.SecureJsonData["tlsClientKey"]) > 0
}
+ for k, v := range ds.SecureJsonData {
+ if len(v) > 0 {
+ dto.EncryptedFields = append(dto.EncryptedFields, k)
+ }
+ }
+
return dto
}
diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go
index 9f8ae329fec..17ac39fa344 100644
--- a/pkg/api/dtos/models.go
+++ b/pkg/api/dtos/models.go
@@ -64,24 +64,24 @@ type DashboardRedirect struct {
}
type DataSource struct {
- Id int64 `json:"id"`
- OrgId int64 `json:"orgId"`
- Name string `json:"name"`
- Type string `json:"type"`
- TypeLogoUrl string `json:"typeLogoUrl"`
- Access m.DsAccess `json:"access"`
- Url string `json:"url"`
- Password string `json:"password"`
- User string `json:"user"`
- Database string `json:"database"`
- BasicAuth bool `json:"basicAuth"`
- BasicAuthUser string `json:"basicAuthUser"`
- BasicAuthPassword string `json:"basicAuthPassword"`
- WithCredentials bool `json:"withCredentials"`
- IsDefault bool `json:"isDefault"`
- JsonData *simplejson.Json `json:"jsonData,omitempty"`
- SecureJsonData map[string]string `json:"secureJsonData,omitempty"`
- TLSAuth TLSAuth `json:"tlsAuth,omitempty"`
+ Id int64 `json:"id"`
+ OrgId int64 `json:"orgId"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ TypeLogoUrl string `json:"typeLogoUrl"`
+ Access m.DsAccess `json:"access"`
+ Url string `json:"url"`
+ Password string `json:"password"`
+ User string `json:"user"`
+ Database string `json:"database"`
+ BasicAuth bool `json:"basicAuth"`
+ BasicAuthUser string `json:"basicAuthUser"`
+ BasicAuthPassword string `json:"basicAuthPassword"`
+ WithCredentials bool `json:"withCredentials"`
+ IsDefault bool `json:"isDefault"`
+ JsonData *simplejson.Json `json:"jsonData,omitempty"`
+ TLSAuth TLSAuth `json:"tlsAuth,omitempty"`
+ EncryptedFields []string `json:"encryptedFields"`
}
// TLSAuth is used to show if TLS certs have been uploaded already
diff --git a/public/app/features/plugins/ds_edit_ctrl.ts b/public/app/features/plugins/ds_edit_ctrl.ts
index 7ac29227001..9c808b188c4 100644
--- a/public/app/features/plugins/ds_edit_ctrl.ts
+++ b/public/app/features/plugins/ds_edit_ctrl.ts
@@ -68,7 +68,6 @@ export class DataSourceEditCtrl {
this.backendSrv.get('/api/datasources/' + id).then(ds => {
this.isNew = false;
this.current = ds;
-
if (datasourceCreated) {
datasourceCreated = false;
this.testDatasource();
diff --git a/public/app/plugins/datasource/cloudwatch/config_ctrl.ts b/public/app/plugins/datasource/cloudwatch/config_ctrl.ts
new file mode 100644
index 00000000000..a60cd6ae8c9
--- /dev/null
+++ b/public/app/plugins/datasource/cloudwatch/config_ctrl.ts
@@ -0,0 +1,51 @@
+///