diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go new file mode 100644 index 00000000000..6e7484df905 --- /dev/null +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -0,0 +1,150 @@ +package cloudwatch + +import ( + "encoding/json" + "errors" + "io/ioutil" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/util" +) + +type actionHandler func(*cwRequest, *middleware.Context) + +var actionHandlers map[string]actionHandler + +type cwRequest struct { + Region string `json:"region"` + Action string `json:"action"` + Body []byte `json:"-"` +} + +func init() { + actionHandlers = map[string]actionHandler{ + "GetMetricStatistics": handleGetMetricStatistics, + "ListMetrics": handleListMetrics, + "DescribeInstances": handleDescribeInstances, + "__GetRegions": handleGetRegions, + } +} + +func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { + svc := cloudwatch.New(&aws.Config{Region: aws.String(req.Region)}) + + reqParam := &struct { + Parameters struct { + Namespace string `json:"namespace"` + MetricName string `json:"metricName"` + Dimensions []*cloudwatch.Dimension `json:"dimensions"` + Statistics []*string `json:"statistics"` + StartTime int64 `json:"startTime"` + EndTime int64 `json:"endTime"` + Period int64 `json:"period"` + } `json:"parameters"` + }{} + json.Unmarshal(req.Body, reqParam) + + params := &cloudwatch.GetMetricStatisticsInput{ + Namespace: aws.String(reqParam.Parameters.Namespace), + MetricName: aws.String(reqParam.Parameters.MetricName), + Dimensions: reqParam.Parameters.Dimensions, + Statistics: reqParam.Parameters.Statistics, + StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)), + EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)), + Period: aws.Int64(reqParam.Parameters.Period), + } + + resp, err := svc.GetMetricStatistics(params) + if err != nil { + c.JsonApiErr(500, "Unable to call AWS API", err) + return + } + + c.JSON(200, resp) +} + +func handleListMetrics(req *cwRequest, c *middleware.Context) { + svc := cloudwatch.New(&aws.Config{Region: aws.String(req.Region)}) + reqParam := &struct { + Parameters struct { + Namespace string `json:"namespace"` + MetricName string `json:"metricName"` + Dimensions []*cloudwatch.DimensionFilter `json:"dimensions"` + } `json:"parameters"` + }{} + + json.Unmarshal(req.Body, reqParam) + + params := &cloudwatch.ListMetricsInput{ + Namespace: aws.String(reqParam.Parameters.Namespace), + MetricName: aws.String(reqParam.Parameters.MetricName), + Dimensions: reqParam.Parameters.Dimensions, + } + + resp, err := svc.ListMetrics(params) + if err != nil { + c.JsonApiErr(500, "Unable to call AWS API", err) + return + } + + c.JSON(200, resp) +} + +func handleDescribeInstances(req *cwRequest, c *middleware.Context) { + svc := ec2.New(&aws.Config{Region: aws.String(req.Region)}) + + reqParam := &struct { + Parameters struct { + Filters []*ec2.Filter `json:"filters"` + InstanceIds []*string `json:"instanceIds"` + } `json:"parameters"` + }{} + json.Unmarshal(req.Body, reqParam) + + params := &ec2.DescribeInstancesInput{} + if len(reqParam.Parameters.Filters) > 0 { + params.Filters = reqParam.Parameters.Filters + } + if len(reqParam.Parameters.InstanceIds) > 0 { + params.InstanceIDs = reqParam.Parameters.InstanceIds + } + + resp, err := svc.DescribeInstances(params) + if err != nil { + c.JsonApiErr(500, "Unable to call AWS API", err) + return + } + + c.JSON(200, resp) +} + +func handleGetRegions(req *cwRequest, c *middleware.Context) { + regions := []string{ + "us-west-2", "us-west-1", "eu-west-1", "eu-central-1", "ap-southeast-1", + "ap-southeast-2", "ap-northeast-1", "sa-east-1", + } + + result := []interface{}{} + for _, region := range regions { + result = append(result, util.DynMap{"text": region, "value": region}) + } + + c.JSON(200, result) +} + +func HandleRequest(c *middleware.Context) { + var req cwRequest + req.Body, _ = ioutil.ReadAll(c.Req.Request.Body) + json.Unmarshal(req.Body, &req) + + if handler, found := actionHandlers[req.Action]; !found { + c.JsonApiErr(500, "Unexpected AWS Action", errors.New(req.Action)) + return + } else { + handler(&req, c) + } +} diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 085fe3886d3..f95ccb97c94 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -8,6 +8,7 @@ import ( "net/url" "time" + "github.com/grafana/grafana/pkg/api/cloudwatch" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" @@ -83,7 +84,7 @@ func ProxyDataSourceRequest(c *middleware.Context) { } if query.Result.Type == m.DS_CLOUDWATCH { - ProxyCloudWatchDataSourceRequest(c) + cloudwatch.HandleRequest(c) } else { proxyPath := c.Params("*") proxy := NewReverseProxy(&ds, proxyPath, targetUrl) diff --git a/pkg/api/dataproxy_cloudwatch.go b/pkg/api/dataproxy_cloudwatch.go deleted file mode 100644 index 2e833c1caf4..00000000000 --- a/pkg/api/dataproxy_cloudwatch.go +++ /dev/null @@ -1,125 +0,0 @@ -package api - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/cloudwatch" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/grafana/grafana/pkg/middleware" -) - -func ProxyCloudWatchDataSourceRequest(c *middleware.Context) { - body, _ := ioutil.ReadAll(c.Req.Request.Body) - - reqInfo := &struct { - Region string `json:"region"` - Service string `json:"service"` - Action string `json:"action"` - }{} - json.Unmarshal([]byte(body), reqInfo) - - switch reqInfo.Service { - case "CloudWatch": - svc := cloudwatch.New(&aws.Config{Region: aws.String(reqInfo.Region)}) - - switch reqInfo.Action { - case "GetMetricStatistics": - reqParam := &struct { - Parameters struct { - Namespace string `json:"namespace"` - MetricName string `json:"metricName"` - Dimensions []*cloudwatch.Dimension `json:"dimensions"` - Statistics []*string `json:"statistics"` - StartTime int64 `json:"startTime"` - EndTime int64 `json:"endTime"` - Period int64 `json:"period"` - } `json:"parameters"` - }{} - json.Unmarshal([]byte(body), reqParam) - - params := &cloudwatch.GetMetricStatisticsInput{ - Namespace: aws.String(reqParam.Parameters.Namespace), - MetricName: aws.String(reqParam.Parameters.MetricName), - Dimensions: reqParam.Parameters.Dimensions, - Statistics: reqParam.Parameters.Statistics, - StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)), - EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)), - Period: aws.Int64(reqParam.Parameters.Period), - } - - resp, err := svc.GetMetricStatistics(params) - if err != nil { - c.JsonApiErr(500, "Unable to call AWS API", err) - return - } - - respJson, _ := json.Marshal(resp) - fmt.Fprint(c.RW(), string(respJson)) - case "ListMetrics": - reqParam := &struct { - Parameters struct { - Namespace string `json:"namespace"` - MetricName string `json:"metricName"` - Dimensions []*cloudwatch.DimensionFilter `json:"dimensions"` - } `json:"parameters"` - }{} - json.Unmarshal([]byte(body), reqParam) - - params := &cloudwatch.ListMetricsInput{ - Namespace: aws.String(reqParam.Parameters.Namespace), - MetricName: aws.String(reqParam.Parameters.MetricName), - Dimensions: reqParam.Parameters.Dimensions, - } - - resp, err := svc.ListMetrics(params) - if err != nil { - c.JsonApiErr(500, "Unable to call AWS API", err) - return - } - - respJson, _ := json.Marshal(resp) - fmt.Fprint(c.RW(), string(respJson)) - default: - c.JsonApiErr(500, "Unexpected CloudWatch action", errors.New(reqInfo.Action)) - } - case "EC2": - svc := ec2.New(&aws.Config{Region: aws.String(reqInfo.Region)}) - - switch reqInfo.Action { - case "DescribeInstances": - reqParam := &struct { - Parameters struct { - Filters []*ec2.Filter `json:"filters"` - InstanceIds []*string `json:"instanceIds"` - } `json:"parameters"` - }{} - json.Unmarshal([]byte(body), reqParam) - - params := &ec2.DescribeInstancesInput{} - if len(reqParam.Parameters.Filters) > 0 { - params.Filters = reqParam.Parameters.Filters - } - if len(reqParam.Parameters.InstanceIds) > 0 { - params.InstanceIDs = reqParam.Parameters.InstanceIds - } - - resp, err := svc.DescribeInstances(params) - if err != nil { - c.JsonApiErr(500, "Unable to call AWS API", err) - return - } - - respJson, _ := json.Marshal(resp) - fmt.Fprint(c.RW(), string(respJson)) - default: - c.JsonApiErr(500, "Unexpected EC2 action", errors.New(reqInfo.Action)) - } - default: - c.JsonApiErr(500, "Unexpected service", errors.New(reqInfo.Service)) - } -} diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 55657ee2071..b8cf0345c83 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -20,9 +20,6 @@ function (angular, _) { this.defaultRegion = datasource.jsonData.defaultRegion; /* jshint -W101 */ - this.supportedRegion = [ - 'us-east-1', 'us-west-2', 'us-west-1', 'eu-west-1', 'eu-central-1', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'sa-east-1' - ]; this.supportedMetrics = { 'AWS/AutoScaling': [ @@ -265,7 +262,6 @@ function (angular, _) { CloudWatchDatasource.prototype.performTimeSeriesQuery = function(query, start, end) { return this.awsRequest({ region: query.region, - service: 'CloudWatch', action: 'GetMetricStatistics', parameters: { namespace: query.namespace, @@ -280,7 +276,7 @@ function (angular, _) { }; CloudWatchDatasource.prototype.getRegions = function() { - return $q.when(this.supportedRegion); + return this.awsRequest({action: '__GetRegions'}); }; CloudWatchDatasource.prototype.getNamespaces = function() { @@ -300,7 +296,6 @@ function (angular, _) { CloudWatchDatasource.prototype.getDimensionValues = function(region, namespace, metricName, dimensions) { var request = { region: templateSrv.replace(region), - service: 'CloudWatch', action: 'ListMetrics', parameters: { namespace: templateSrv.replace(namespace), @@ -321,7 +316,6 @@ function (angular, _) { CloudWatchDatasource.prototype.performEC2DescribeInstances = function(region, filters, instanceIds) { return this.awsRequest({ region: region, - service: 'EC2', action: 'DescribeInstances', parameters: { filter: filters, @@ -341,12 +335,12 @@ function (angular, _) { }); }; - var regionQuery = query.match(/^region\(\)/); + var regionQuery = query.match(/^regions\(\)/); if (regionQuery) { - return this.getRegions().then(transformSuggestData); + return this.getRegions(); } - var namespaceQuery = query.match(/^namespace\(\)/); + var namespaceQuery = query.match(/^namespaces\(\)/); if (namespaceQuery) { return this.getNamespaces().then(transformSuggestData); } diff --git a/public/app/plugins/datasource/cloudwatch/query_ctrl.js b/public/app/plugins/datasource/cloudwatch/query_ctrl.js index 7c8b44b7f2a..08e2845a0e1 100644 --- a/public/app/plugins/datasource/cloudwatch/query_ctrl.js +++ b/public/app/plugins/datasource/cloudwatch/query_ctrl.js @@ -25,12 +25,12 @@ function (angular, _) { }; $scope.getRegions = function() { - return $scope.datasource.metricFindQuery('region()') + return $scope.datasource.metricFindQuery('regions()') .then($scope.transformToSegments(true)); }; $scope.getNamespaces = function() { - return $scope.datasource.metricFindQuery('namespace()') + return $scope.datasource.metricFindQuery('namespaces()') .then($scope.transformToSegments(true)); }; diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index 807b3b010b6..b3f70cfaead 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -108,17 +108,27 @@ describe('CloudWatchDatasource', function() { }; }); - it('should return suggest list for region()', function(done) { - var query = 'region()'; - ctx.ds.metricFindQuery(query).then(function(result) { - expect(result[0].text).to.contain('us-east-1'); - done(); + describe('regions()', () => { + let params, result; + beforeEach(() => { + ctx.backendSrv.datasourceRequest = args => { + params = args; + return ctx.$q.when({data: [{text: 'us-east-1'}]}); + }; + ctx.ds.metricFindQuery("regions()").then(args => { + result = args; + }); + ctx.$rootScope.$apply(); + }); + + it('should issue __GetRegions request', () => { + expect(result[0].text).to.contain('us-east-1'); + expect(params.data.action).to.be('__GetRegions'); }); - ctx.$rootScope.$apply(); }); it('should return suggest list for namespace()', function(done) { - var query = 'namespace()'; + var query = 'namespaces()'; ctx.ds.metricFindQuery(query).then(function(result) { result = result.map(function(v) { return v.text; }); expect(result).to.contain('AWS/EC2');