From 8fba6dcb0d26e8a46de607c67a8048b1af9406c5 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Tue, 19 Sep 2017 18:55:11 +0900 Subject: [PATCH] re-implement ec2_instance_attribute() --- pkg/tsdb/cloudwatch/metric_find_query.go | 73 +++++++++++++++++++ .../datasource/cloudwatch/datasource.js | 57 +++++++-------- 2 files changed, 98 insertions(+), 32 deletions(-) diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 42f5eed510e..80ca06113b2 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -3,6 +3,7 @@ package cloudwatch import ( "context" "errors" + "reflect" "sort" "strings" "sync" @@ -178,6 +179,9 @@ func (e *CloudWatchExecutor) executeMetricFindQuery(ctx context.Context, queries case "ebs_volume_ids": data, err = e.handleGetEbsVolumeIds(ctx, parameters, queryContext) break + case "ec2_instance_attribute": + data, err = e.handleGetEc2InstanceAttribute(ctx, parameters, queryContext) + break } if err != nil { queryResult.Error = err @@ -342,6 +346,75 @@ func (e *CloudWatchExecutor) handleGetEbsVolumeIds(ctx context.Context, paramete return result, nil } +func (e *CloudWatchExecutor) handleGetEc2InstanceAttribute(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.QueryContext) ([]suggestData, error) { + region := parameters.Get("region").MustString() + attributeName := parameters.Get("attributeName").MustString() + filterJson := parameters.Get("filters").MustMap() + + var filters []*ec2.Filter + for k, v := range filterJson { + if vv, ok := v.([]string); ok { + var vvvv []*string + for _, vvv := range vv { + vvvv = append(vvvv, &vvv) + } + filters = append(filters, &ec2.Filter{ + Name: aws.String(k), + Values: vvvv, + }) + } + } + + instances, err := e.ec2DescribeInstances(region, filters, nil) + if err != nil { + return nil, err + } + + result := make([]suggestData, 0) + dupCheck := make(map[string]bool) + for _, instance := range instances.Reservations[0].Instances { + tags := make(map[string]string) + for _, tag := range instance.Tags { + tags[*tag.Key] = *tag.Value + } + + var data string + if strings.Index(attributeName, "Tags.") == 0 { + tagName := attributeName[5:] + data = tags[tagName] + } else { + attributePath := strings.Split(attributeName, ".") + v := reflect.ValueOf(instance) + for _, key := range attributePath { + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() != reflect.Struct { + return nil, errors.New("invalid attribute path") + } + v = v.FieldByName(key) + } + if attr, ok := v.Interface().(*string); ok { + data = *attr + } else { + return nil, errors.New("invalid attribute path") + } + } + + if _, exists := dupCheck[data]; exists { + continue + } + dupCheck[data] = true + result = append(result, suggestData{Text: data, Value: data}) + } + + sort.Slice(result, func(i, j int) bool { + return result[i].Text < result[j].Text + }) + + return result, nil +} + func getAwsConfig(dsInfo *cwapi.DatasourceInfo) (*aws.Config, error) { creds, err := cwapi.GetCredentials(dsInfo) if err != nil { diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 40fba8af36f..12562a1fb88 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -270,6 +270,29 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot }).then(function (r) { return transformSuggestDataFromTable(r); }); }; + this.getEc2InstanceAttribute = function(region, attributeName, filters) { + var range = timeSrv.timeRange(); + return backendSrv.post('/api/tsdb/query', { + from: range.from, + to: range.to, + queries: [ + { + refId: 'metricFindQuery', + intervalMs: 1, // dummy + maxDataPoints: 1, // dummy + datasourceId: this.instanceSettings.id, + type: 'metricFindQuery', + subtype: 'ec2_instance_attribute', + parameters: { + region: region, + attributeName: attributeName, + filters: filters + } + } + ] + }).then(function (r) { return transformSuggestDataFromTable(r); }); + }; + this.performEC2DescribeInstances = function(region, filters, instanceIds) { return this.awsRequest({ region: region, @@ -283,12 +306,6 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot var namespace; var metricName; - var transformSuggestData = function(suggestData) { - return _.map(suggestData, function(v) { - return { text: v }; - }); - }; - var regionQuery = query.match(/^regions\(\)/); if (regionQuery) { return this.getRegions(); @@ -329,33 +346,9 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot var ec2InstanceAttributeQuery = query.match(/^ec2_instance_attribute\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/); if (ec2InstanceAttributeQuery) { region = templateSrv.replace(ec2InstanceAttributeQuery[1]); - var filterJson = JSON.parse(templateSrv.replace(ec2InstanceAttributeQuery[3])); - var filters = _.map(filterJson, function(values, name) { - return { - Name: name, - Values: values - }; - }); var targetAttributeName = templateSrv.replace(ec2InstanceAttributeQuery[2]); - - return this.performEC2DescribeInstances(region, filters, null).then(function(result) { - var attributes = _.chain(result.Reservations) - .map(function(reservations) { - return _.map(reservations.Instances, function(instance) { - var tags = {}; - _.each(instance.Tags, function(tag) { - tags[tag.Key] = tag.Value; - }); - instance.Tags = tags; - return instance; - }); - }) - .map(function(instances) { - return _.map(instances, targetAttributeName); - }) - .flatten().uniq().sortBy().value(); - return transformSuggestData(attributes); - }); + var filterJson = JSON.parse(templateSrv.replace(ec2InstanceAttributeQuery[3])); + return this.getEc2InstanceAttribute(region, targetAttributeName, filterJson); } return $q.when([]);