diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 8acddfbcbd0..77b41f1669a 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -97,39 +97,13 @@ func (e *cloudWatchExecutor) handleGetEc2InstanceAttribute(ctx context.Context, dupCheck := make(map[string]bool) for _, reservation := range instances.Reservations { for _, instance := range reservation.Instances { - tags := make(map[string]string) - for _, tag := range instance.Tags { - tags[*tag.Key] = *tag.Value + data, found, err := getInstanceAttributeValue(attributeName, instance) + if err != nil { + return nil, err } - - 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 !v.IsValid() { - return nil, errors.New("invalid attribute path") - } - } - if attr, ok := v.Interface().(*string); ok { - data = *attr - } else if attr, ok := v.Interface().(*time.Time); ok { - data = attr.String() - } else { - return nil, errors.New("invalid attribute path") - } + if !found { + continue } - if _, exists := dupCheck[data]; exists { continue } @@ -146,6 +120,54 @@ func (e *cloudWatchExecutor) handleGetEc2InstanceAttribute(ctx context.Context, return result, nil } +func getInstanceAttributeValue(attributeName string, instance *ec2.Instance) (value string, found bool, err error) { + 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 { + if v.IsNil() { + return "", false, nil + } + v = v.Elem() + } + if v.Kind() != reflect.Struct { + return "", false, errors.New("invalid attribute path") + } + v = v.FieldByName(key) + if !v.IsValid() { + return "", false, errors.New("invalid attribute path") + } + } + + if v.Kind() == reflect.Ptr && v.IsNil() { + return "", false, nil + } + if attr, ok := v.Interface().(*string); ok { + data = *attr + } else if attr, ok := v.Interface().(*time.Time); ok { + data = attr.String() + } else if _, ok := v.Interface().(*bool); ok { + data = fmt.Sprint(v.Elem().Bool()) + } else if v.Kind() == reflect.Ptr && v.Elem().CanInt() { + data = fmt.Sprint(v.Elem().Int()) + } else { + return "", false, errors.New("cannot parse attribute") + } + } + + return data, true, nil +} + func (e *cloudWatchExecutor) handleGetResourceArns(ctx context.Context, pluginCtx backend.PluginContext, parameters url.Values) ([]suggestData, error) { region := parameters.Get("region") resourceType := parameters.Get("resourceType") diff --git a/pkg/tsdb/cloudwatch/metric_find_query_test.go b/pkg/tsdb/cloudwatch/metric_find_query_test.go index 400c3041620..85329bd5760 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query_test.go +++ b/pkg/tsdb/cloudwatch/metric_find_query_test.go @@ -80,6 +80,86 @@ func TestQuery_InstanceAttributes(t *testing.T) { } assert.Equal(t, expResponse, resp) }) + + t.Run("Get different types", func(t *testing.T) { + var expectedInt int64 = 3 + var expectedBool = true + var expectedArn = "arn" + cli = oldEC2Client{ + reservations: []*ec2.Reservation{ + { + Instances: []*ec2.Instance{ + { + AmiLaunchIndex: &expectedInt, + EbsOptimized: &expectedBool, + IamInstanceProfile: &ec2.IamInstanceProfile{ + Arn: &expectedArn, + }, + }, + }, + }, + }, + } + + im := datasource.NewInstanceManager(func(ctx context.Context, s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { + return DataSource{Settings: models.CloudWatchSettings{}, sessions: &fakeSessionCache{}}, nil + }) + + executor := newExecutor(im, log.NewNullLogger()) + + testcases := []struct { + name string + attributeName string + expResponse []suggestData + }{ + { + "int field", + "AmiLaunchIndex", + []suggestData{ + {Text: "3", Value: "3", Label: "3"}, + }, + }, + { + "bool field", + "EbsOptimized", + []suggestData{ + {Text: "true", Value: "true", Label: "true"}, + }, + }, + { + "nested field", + "IamInstanceProfile.Arn", + []suggestData{ + {Text: expectedArn, Value: expectedArn, Label: expectedArn}, + }, + }, + { + "nil field", + "InstanceLifecycle", + []suggestData{}, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + filterMap := map[string][]string{} + filterJson, err := json.Marshal(filterMap) + require.NoError(t, err) + + resp, err := executor.handleGetEc2InstanceAttribute( + context.Background(), + backend.PluginContext{ + DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}, + }, url.Values{ + "region": []string{"us-east-1"}, + "attributeName": []string{tc.attributeName}, + "filters": []string{string(filterJson)}, + }, + ) + require.NoError(t, err) + assert.Equal(t, tc.expResponse, resp) + }) + } + }) } func TestQuery_EBSVolumeIDs(t *testing.T) {