package cloudwatch import ( "context" "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2/ec2iface" "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface" "github.com/grafana/grafana-aws-sdk/pkg/awsds" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/experimental/featuretoggles" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/stretchr/testify/mock" ) type fakeCWLogsClient struct { cloudwatchlogsiface.CloudWatchLogsAPI calls logsQueryCalls logGroups []cloudwatchlogs.DescribeLogGroupsOutput logGroupFields cloudwatchlogs.GetLogGroupFieldsOutput queryResults cloudwatchlogs.GetQueryResultsOutput logGroupsIndex int } type logsQueryCalls struct { startQueryWithContext []*cloudwatchlogs.StartQueryInput getEventsWithContext []*cloudwatchlogs.GetLogEventsInput describeLogGroups []*cloudwatchlogs.DescribeLogGroupsInput } func (m *fakeCWLogsClient) GetQueryResultsWithContext(ctx context.Context, input *cloudwatchlogs.GetQueryResultsInput, option ...request.Option) (*cloudwatchlogs.GetQueryResultsOutput, error) { return &m.queryResults, nil } func (m *fakeCWLogsClient) StartQueryWithContext(ctx context.Context, input *cloudwatchlogs.StartQueryInput, option ...request.Option) (*cloudwatchlogs.StartQueryOutput, error) { m.calls.startQueryWithContext = append(m.calls.startQueryWithContext, input) return &cloudwatchlogs.StartQueryOutput{ QueryId: aws.String("abcd-efgh-ijkl-mnop"), }, nil } func (m *fakeCWLogsClient) StopQueryWithContext(ctx context.Context, input *cloudwatchlogs.StopQueryInput, option ...request.Option) (*cloudwatchlogs.StopQueryOutput, error) { return &cloudwatchlogs.StopQueryOutput{ Success: aws.Bool(true), }, nil } type mockLogsSyncClient struct { cloudwatchlogsiface.CloudWatchLogsAPI mock.Mock } func (m *mockLogsSyncClient) GetQueryResultsWithContext(ctx context.Context, input *cloudwatchlogs.GetQueryResultsInput, option ...request.Option) (*cloudwatchlogs.GetQueryResultsOutput, error) { args := m.Called(ctx, input, option) return args.Get(0).(*cloudwatchlogs.GetQueryResultsOutput), args.Error(1) } func (m *mockLogsSyncClient) StartQueryWithContext(ctx context.Context, input *cloudwatchlogs.StartQueryInput, option ...request.Option) (*cloudwatchlogs.StartQueryOutput, error) { args := m.Called(ctx, input, option) return args.Get(0).(*cloudwatchlogs.StartQueryOutput), args.Error(1) } func (m *fakeCWLogsClient) DescribeLogGroupsWithContext(ctx context.Context, input *cloudwatchlogs.DescribeLogGroupsInput, option ...request.Option) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { m.calls.describeLogGroups = append(m.calls.describeLogGroups, input) output := &m.logGroups[m.logGroupsIndex] m.logGroupsIndex++ return output, nil } func (m *fakeCWLogsClient) GetLogGroupFieldsWithContext(ctx context.Context, input *cloudwatchlogs.GetLogGroupFieldsInput, option ...request.Option) (*cloudwatchlogs.GetLogGroupFieldsOutput, error) { return &m.logGroupFields, nil } func (m *fakeCWLogsClient) GetLogEventsWithContext(ctx context.Context, input *cloudwatchlogs.GetLogEventsInput, option ...request.Option) (*cloudwatchlogs.GetLogEventsOutput, error) { m.calls.getEventsWithContext = append(m.calls.getEventsWithContext, input) return &cloudwatchlogs.GetLogEventsOutput{ Events: []*cloudwatchlogs.OutputLogEvent{}, }, nil } type fakeCWAnnotationsClient struct { cloudwatchiface.CloudWatchAPI calls annontationsQueryCalls describeAlarmsForMetricOutput *cloudwatch.DescribeAlarmsForMetricOutput describeAlarmsOutput *cloudwatch.DescribeAlarmsOutput } type annontationsQueryCalls struct { describeAlarmsForMetric []*cloudwatch.DescribeAlarmsForMetricInput describeAlarms []*cloudwatch.DescribeAlarmsInput } func (c *fakeCWAnnotationsClient) DescribeAlarmsForMetric(params *cloudwatch.DescribeAlarmsForMetricInput) (*cloudwatch.DescribeAlarmsForMetricOutput, error) { c.calls.describeAlarmsForMetric = append(c.calls.describeAlarmsForMetric, params) return c.describeAlarmsForMetricOutput, nil } func (c *fakeCWAnnotationsClient) DescribeAlarms(params *cloudwatch.DescribeAlarmsInput) (*cloudwatch.DescribeAlarmsOutput, error) { c.calls.describeAlarms = append(c.calls.describeAlarms, params) return c.describeAlarmsOutput, nil } type mockEC2Client struct { mock.Mock } func (c *mockEC2Client) DescribeRegionsWithContext(ctx aws.Context, in *ec2.DescribeRegionsInput, option ...request.Option) (*ec2.DescribeRegionsOutput, error) { args := c.Called(in) return args.Get(0).(*ec2.DescribeRegionsOutput), args.Error(1) } func (c *mockEC2Client) DescribeInstancesPagesWithContext(ctx aws.Context, in *ec2.DescribeInstancesInput, fn func(*ec2.DescribeInstancesOutput, bool) bool, opts ...request.Option) error { args := c.Called(in, fn) return args.Error(0) } // Please use mockEC2Client above, we are slowly migrating towards using testify's mocks only type oldEC2Client struct { ec2iface.EC2API regions []string reservations []*ec2.Reservation } func (c oldEC2Client) DescribeRegionsWithContext(ctx aws.Context, in *ec2.DescribeRegionsInput, option ...request.Option) (*ec2.DescribeRegionsOutput, error) { regions := []*ec2.Region{} for _, region := range c.regions { regions = append(regions, &ec2.Region{ RegionName: aws.String(region), }) } return &ec2.DescribeRegionsOutput{ Regions: regions, }, nil } func (c oldEC2Client) DescribeInstancesPagesWithContext(ctx aws.Context, in *ec2.DescribeInstancesInput, fn func(*ec2.DescribeInstancesOutput, bool) bool, opts ...request.Option) error { reservations := []*ec2.Reservation{} for _, r := range c.reservations { instances := []*ec2.Instance{} for _, inst := range r.Instances { if len(in.InstanceIds) == 0 { instances = append(instances, inst) continue } for _, id := range in.InstanceIds { if *inst.InstanceId == *id { instances = append(instances, inst) } } } reservation := &ec2.Reservation{Instances: instances} reservations = append(reservations, reservation) } fn(&ec2.DescribeInstancesOutput{ Reservations: reservations, }, true) return nil } type fakeRGTAClient struct { resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI tagMapping []*resourcegroupstaggingapi.ResourceTagMapping } func (c fakeRGTAClient) GetResourcesPagesWithContext(ctx context.Context, in *resourcegroupstaggingapi.GetResourcesInput, fn func(*resourcegroupstaggingapi.GetResourcesOutput, bool) bool, opts ...request.Option) error { fn(&resourcegroupstaggingapi.GetResourcesOutput{ ResourceTagMappingList: c.tagMapping, }, true) return nil } type fakeCheckHealthClient struct { listMetricsPages func(input *cloudwatch.ListMetricsInput, fn func(*cloudwatch.ListMetricsOutput, bool) bool) error describeLogGroups func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) } func (c fakeCheckHealthClient) ListMetricsPagesWithContext(ctx aws.Context, input *cloudwatch.ListMetricsInput, fn func(*cloudwatch.ListMetricsOutput, bool) bool, opts ...request.Option) error { if c.listMetricsPages != nil { return c.listMetricsPages(input, fn) } return nil } func (c fakeCheckHealthClient) DescribeLogGroupsWithContext(ctx context.Context, input *cloudwatchlogs.DescribeLogGroupsInput, option ...request.Option) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { if c.describeLogGroups != nil { return c.describeLogGroups(input) } return nil, nil } func (c fakeCheckHealthClient) GetLogGroupFieldsWithContext(ctx context.Context, input *cloudwatchlogs.GetLogGroupFieldsInput, option ...request.Option) (*cloudwatchlogs.GetLogGroupFieldsOutput, error) { return nil, nil } func testInstanceManager(pageLimit int) instancemgmt.InstanceManager { return datasource.NewInstanceManager((func(ctx context.Context, s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return DataSource{Settings: models.CloudWatchSettings{ AWSDatasourceSettings: awsds.AWSDatasourceSettings{ Region: "us-east-1", }, GrafanaSettings: awsds.AuthSettings{ListMetricsPageLimit: pageLimit}, }}, nil })) } func defaultTestInstanceManager() instancemgmt.InstanceManager { return testInstanceManager(1000) } type mockSessionCache struct { mock.Mock } func (c *mockSessionCache) GetSession(config awsds.SessionConfig) (*session.Session, error) { args := c.Called(config) return args.Get(0).(*session.Session), args.Error(1) } type fakeSessionCache struct { getSession func(c awsds.SessionConfig) (*session.Session, error) calledRegions []string } func (s *fakeSessionCache) GetSession(c awsds.SessionConfig) (*session.Session, error) { s.calledRegions = append(s.calledRegions, c.Settings.Region) if s.getSession != nil { return s.getSession(c) } return &session.Session{ Config: &aws.Config{}, }, nil } type mockedCallResourceResponseSenderForOauth struct { Response *backend.CallResourceResponse } func (s *mockedCallResourceResponseSenderForOauth) Send(resp *backend.CallResourceResponse) error { s.Response = resp return nil } type fakeAWSError struct { code string message string } func (e fakeAWSError) OrigErr() error { return nil } func (e fakeAWSError) Error() string { return e.message } func (e fakeAWSError) Code() string { return e.code } func (e fakeAWSError) Message() string { return e.message } func contextWithFeaturesEnabled(enabled ...string) context.Context { featureString := strings.Join(enabled, ",") cfg := backend.NewGrafanaCfg(map[string]string{featuretoggles.EnabledFeatures: featureString}) return backend.WithGrafanaConfig(context.Background(), cfg) }