mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudWatch: Log group variable should get all log groups (#54062)
This commit is contained in:
parent
1f17e9a044
commit
4f2b66ac1d
@ -19,8 +19,8 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
limitExceededException = "LimitExceededException"
|
limitExceededException = "LimitExceededException"
|
||||||
defaultLimit = int64(10)
|
defaultEventLimit = int64(10)
|
||||||
logGroupDefaultLimit = int64(50)
|
defaultLogGroupLimit = int64(50)
|
||||||
)
|
)
|
||||||
|
|
||||||
type AWSError struct {
|
type AWSError struct {
|
||||||
@ -128,6 +128,8 @@ func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, model LogQuer
|
|||||||
switch model.SubType {
|
switch model.SubType {
|
||||||
case "DescribeLogGroups":
|
case "DescribeLogGroups":
|
||||||
data, err = e.handleDescribeLogGroups(ctx, logsClient, model)
|
data, err = e.handleDescribeLogGroups(ctx, logsClient, model)
|
||||||
|
case "DescribeAllLogGroups":
|
||||||
|
data, err = e.handleDescribeAllLogGroups(ctx, logsClient, model)
|
||||||
case "GetLogGroupFields":
|
case "GetLogGroupFields":
|
||||||
data, err = e.handleGetLogGroupFields(ctx, logsClient, model, query.RefID)
|
data, err = e.handleGetLogGroupFields(ctx, logsClient, model, query.RefID)
|
||||||
case "StartQuery":
|
case "StartQuery":
|
||||||
@ -148,7 +150,7 @@ func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, model LogQuer
|
|||||||
|
|
||||||
func (e *cloudWatchExecutor) handleGetLogEvents(ctx context.Context, logsClient cloudwatchlogsiface.CloudWatchLogsAPI,
|
func (e *cloudWatchExecutor) handleGetLogEvents(ctx context.Context, logsClient cloudwatchlogsiface.CloudWatchLogsAPI,
|
||||||
parameters LogQueryJson) (*data.Frame, error) {
|
parameters LogQueryJson) (*data.Frame, error) {
|
||||||
limit := defaultLimit
|
limit := defaultEventLimit
|
||||||
if parameters.Limit != nil && *parameters.Limit > 0 {
|
if parameters.Limit != nil && *parameters.Limit > 0 {
|
||||||
limit = *parameters.Limit
|
limit = *parameters.Limit
|
||||||
}
|
}
|
||||||
@ -203,7 +205,7 @@ func (e *cloudWatchExecutor) handleGetLogEvents(ctx context.Context, logsClient
|
|||||||
|
|
||||||
func (e *cloudWatchExecutor) handleDescribeLogGroups(ctx context.Context,
|
func (e *cloudWatchExecutor) handleDescribeLogGroups(ctx context.Context,
|
||||||
logsClient cloudwatchlogsiface.CloudWatchLogsAPI, parameters LogQueryJson) (*data.Frame, error) {
|
logsClient cloudwatchlogsiface.CloudWatchLogsAPI, parameters LogQueryJson) (*data.Frame, error) {
|
||||||
logGroupLimit := logGroupDefaultLimit
|
logGroupLimit := defaultLogGroupLimit
|
||||||
if parameters.Limit != nil && *parameters.Limit != 0 {
|
if parameters.Limit != nil && *parameters.Limit != 0 {
|
||||||
logGroupLimit = *parameters.Limit
|
logGroupLimit = *parameters.Limit
|
||||||
}
|
}
|
||||||
@ -235,6 +237,40 @@ func (e *cloudWatchExecutor) handleDescribeLogGroups(ctx context.Context,
|
|||||||
return frame, nil
|
return frame, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *cloudWatchExecutor) handleDescribeAllLogGroups(ctx context.Context, logsClient cloudwatchlogsiface.CloudWatchLogsAPI, parameters LogQueryJson) (*data.Frame, error) {
|
||||||
|
var namePrefix, nextToken *string
|
||||||
|
if len(parameters.LogGroupNamePrefix) != 0 {
|
||||||
|
namePrefix = aws.String(parameters.LogGroupNamePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response *cloudwatchlogs.DescribeLogGroupsOutput
|
||||||
|
var err error
|
||||||
|
logGroupNames := []*string{}
|
||||||
|
for {
|
||||||
|
response, err = logsClient.DescribeLogGroupsWithContext(ctx, &cloudwatchlogs.DescribeLogGroupsInput{
|
||||||
|
LogGroupNamePrefix: namePrefix,
|
||||||
|
NextToken: nextToken,
|
||||||
|
Limit: aws.Int64(defaultLogGroupLimit),
|
||||||
|
})
|
||||||
|
if err != nil || response == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, logGroup := range response.LogGroups {
|
||||||
|
logGroupNames = append(logGroupNames, logGroup.LogGroupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.NextToken == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nextToken = response.NextToken
|
||||||
|
}
|
||||||
|
|
||||||
|
groupNamesField := data.NewField("logGroupName", nil, logGroupNames)
|
||||||
|
frame := data.NewFrame("logGroups", groupNamesField)
|
||||||
|
return frame, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient cloudwatchlogsiface.CloudWatchLogsAPI,
|
func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient cloudwatchlogsiface.CloudWatchLogsAPI,
|
||||||
parameters LogQueryJson, timeRange backend.TimeRange) (*cloudwatchlogs.StartQueryOutput, error) {
|
parameters LogQueryJson, timeRange backend.TimeRange) (*cloudwatchlogs.StartQueryOutput, error) {
|
||||||
startTime := timeRange.From
|
startTime := timeRange.From
|
||||||
|
@ -120,8 +120,8 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Empty log group name prefix", func(t *testing.T) {
|
t.Run("Empty log group name prefix", func(t *testing.T) {
|
||||||
cli = fakeCWLogsClient{
|
cli = fakeCWLogsClient{
|
||||||
logGroups: cloudwatchlogs.DescribeLogGroupsOutput{
|
logGroups: []cloudwatchlogs.DescribeLogGroupsOutput{
|
||||||
LogGroups: []*cloudwatchlogs.LogGroup{
|
{LogGroups: []*cloudwatchlogs.LogGroup{
|
||||||
{
|
{
|
||||||
LogGroupName: aws.String("group_a"),
|
LogGroupName: aws.String("group_a"),
|
||||||
},
|
},
|
||||||
@ -131,7 +131,7 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
|
|||||||
{
|
{
|
||||||
LogGroupName: aws.String("group_c"),
|
LogGroupName: aws.String("group_c"),
|
||||||
},
|
},
|
||||||
},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,8 +176,8 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Non-empty log group name prefix", func(t *testing.T) {
|
t.Run("Non-empty log group name prefix", func(t *testing.T) {
|
||||||
cli = fakeCWLogsClient{
|
cli = fakeCWLogsClient{
|
||||||
logGroups: cloudwatchlogs.DescribeLogGroupsOutput{
|
logGroups: []cloudwatchlogs.DescribeLogGroupsOutput{
|
||||||
LogGroups: []*cloudwatchlogs.LogGroup{
|
{LogGroups: []*cloudwatchlogs.LogGroup{
|
||||||
{
|
{
|
||||||
LogGroupName: aws.String("group_a"),
|
LogGroupName: aws.String("group_a"),
|
||||||
},
|
},
|
||||||
@ -187,7 +187,7 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
|
|||||||
{
|
{
|
||||||
LogGroupName: aws.String("group_c"),
|
LogGroupName: aws.String("group_c"),
|
||||||
},
|
},
|
||||||
},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,6 +233,92 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQuery_DescribeAllLogGroups(t *testing.T) {
|
||||||
|
origNewCWLogsClient := NewCWLogsClient
|
||||||
|
t.Cleanup(func() {
|
||||||
|
NewCWLogsClient = origNewCWLogsClient
|
||||||
|
})
|
||||||
|
|
||||||
|
var cli fakeCWLogsClient
|
||||||
|
|
||||||
|
NewCWLogsClient = func(sess *session.Session) cloudwatchlogsiface.CloudWatchLogsAPI {
|
||||||
|
return &cli
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("multiple batches", func(t *testing.T) {
|
||||||
|
token := "foo"
|
||||||
|
cli = fakeCWLogsClient{
|
||||||
|
logGroups: []cloudwatchlogs.DescribeLogGroupsOutput{
|
||||||
|
{
|
||||||
|
LogGroups: []*cloudwatchlogs.LogGroup{
|
||||||
|
{
|
||||||
|
LogGroupName: aws.String("group_a"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
LogGroupName: aws.String("group_b"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
LogGroupName: aws.String("group_c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NextToken: &token,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
LogGroups: []*cloudwatchlogs.LogGroup{
|
||||||
|
{
|
||||||
|
LogGroupName: aws.String("group_x"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
LogGroupName: aws.String("group_y"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
LogGroupName: aws.String("group_z"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
||||||
|
return datasourceInfo{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures())
|
||||||
|
resp, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{
|
||||||
|
PluginContext: backend.PluginContext{
|
||||||
|
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{},
|
||||||
|
},
|
||||||
|
Queries: []backend.DataQuery{
|
||||||
|
{
|
||||||
|
JSON: json.RawMessage(`{
|
||||||
|
"type": "logAction",
|
||||||
|
"subtype": "DescribeAllLogGroups",
|
||||||
|
"limit": 50
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, resp)
|
||||||
|
|
||||||
|
assert.Equal(t, &backend.QueryDataResponse{Responses: backend.Responses{
|
||||||
|
"": backend.DataResponse{
|
||||||
|
Frames: data.Frames{
|
||||||
|
&data.Frame{
|
||||||
|
Name: "logGroups",
|
||||||
|
Fields: []*data.Field{
|
||||||
|
data.NewField("logGroupName", nil, []*string{
|
||||||
|
aws.String("group_a"), aws.String("group_b"), aws.String("group_c"), aws.String("group_x"), aws.String("group_y"), aws.String("group_z"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestQuery_GetLogGroupFields(t *testing.T) {
|
func TestQuery_GetLogGroupFields(t *testing.T) {
|
||||||
origNewCWLogsClient := NewCWLogsClient
|
origNewCWLogsClient := NewCWLogsClient
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
@ -23,9 +23,11 @@ type fakeCWLogsClient struct {
|
|||||||
|
|
||||||
calls logsQueryCalls
|
calls logsQueryCalls
|
||||||
|
|
||||||
logGroups cloudwatchlogs.DescribeLogGroupsOutput
|
logGroups []cloudwatchlogs.DescribeLogGroupsOutput
|
||||||
logGroupFields cloudwatchlogs.GetLogGroupFieldsOutput
|
logGroupFields cloudwatchlogs.GetLogGroupFieldsOutput
|
||||||
queryResults cloudwatchlogs.GetQueryResultsOutput
|
queryResults cloudwatchlogs.GetQueryResultsOutput
|
||||||
|
|
||||||
|
logGroupsIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
type logsQueryCalls struct {
|
type logsQueryCalls struct {
|
||||||
@ -52,7 +54,9 @@ func (m *fakeCWLogsClient) StopQueryWithContext(ctx context.Context, input *clou
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *fakeCWLogsClient) DescribeLogGroupsWithContext(ctx context.Context, input *cloudwatchlogs.DescribeLogGroupsInput, option ...request.Option) (*cloudwatchlogs.DescribeLogGroupsOutput, error) {
|
func (m *fakeCWLogsClient) DescribeLogGroupsWithContext(ctx context.Context, input *cloudwatchlogs.DescribeLogGroupsInput, option ...request.Option) (*cloudwatchlogs.DescribeLogGroupsOutput, error) {
|
||||||
return &m.logGroups, nil
|
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) {
|
func (m *fakeCWLogsClient) GetLogGroupFieldsWithContext(ctx context.Context, input *cloudwatchlogs.GetLogGroupFieldsInput, option ...request.Option) (*cloudwatchlogs.GetLogGroupFieldsOutput, error) {
|
||||||
|
@ -474,6 +474,13 @@ export class CloudWatchDatasource
|
|||||||
return logGroupNames;
|
return logGroupNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async describeAllLogGroups(params: DescribeLogGroupsRequest): Promise<string[]> {
|
||||||
|
const dataFrames = await lastValueFrom(this.makeLogActionRequest('DescribeAllLogGroups', [params]));
|
||||||
|
|
||||||
|
const logGroupNames = dataFrames[0]?.fields[0]?.values.toArray() ?? [];
|
||||||
|
return logGroupNames;
|
||||||
|
}
|
||||||
|
|
||||||
async getLogGroupFields(params: GetLogGroupFieldsRequest): Promise<GetLogGroupFieldsResponse> {
|
async getLogGroupFields(params: GetLogGroupFieldsRequest): Promise<GetLogGroupFieldsResponse> {
|
||||||
const dataFrames = await lastValueFrom(this.makeLogActionRequest('GetLogGroupFields', [params]));
|
const dataFrames = await lastValueFrom(this.makeLogActionRequest('GetLogGroupFields', [params]));
|
||||||
|
|
||||||
@ -663,9 +670,7 @@ export class CloudWatchDatasource
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: seems to be some sort of bug that we don't really send region with all queries. This means
|
|
||||||
// if you select different than default region in editor you will get results for autocomplete from wrong
|
|
||||||
// region.
|
|
||||||
if (anyQuery.region) {
|
if (anyQuery.region) {
|
||||||
anyQuery.region = this.replace(anyQuery.region, options.scopedVars, true, 'region');
|
anyQuery.region = this.replace(anyQuery.region, options.scopedVars, true, 'region');
|
||||||
anyQuery.region = this.getActualRegion(anyQuery.region);
|
anyQuery.region = this.getActualRegion(anyQuery.region);
|
||||||
|
@ -77,6 +77,7 @@ export interface CloudWatchMathExpressionQuery extends DataQuery {
|
|||||||
|
|
||||||
export type LogAction =
|
export type LogAction =
|
||||||
| 'DescribeLogGroups'
|
| 'DescribeLogGroups'
|
||||||
|
| 'DescribeAllLogGroups'
|
||||||
| 'GetQueryResults'
|
| 'GetQueryResults'
|
||||||
| 'GetLogGroupFields'
|
| 'GetLogGroupFields'
|
||||||
| 'GetLogEvents'
|
| 'GetLogEvents'
|
||||||
|
@ -19,7 +19,7 @@ ds.datasource.getRegions = jest.fn().mockResolvedValue([{ label: 'a', value: 'a'
|
|||||||
ds.datasource.getNamespaces = jest.fn().mockResolvedValue([{ label: 'b', value: 'b' }]);
|
ds.datasource.getNamespaces = jest.fn().mockResolvedValue([{ label: 'b', value: 'b' }]);
|
||||||
ds.datasource.getMetrics = jest.fn().mockResolvedValue([{ label: 'c', value: 'c' }]);
|
ds.datasource.getMetrics = jest.fn().mockResolvedValue([{ label: 'c', value: 'c' }]);
|
||||||
ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([{ label: 'd', value: 'd' }]);
|
ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([{ label: 'd', value: 'd' }]);
|
||||||
ds.datasource.describeLogGroups = jest.fn().mockResolvedValue(['a', 'b']);
|
ds.datasource.describeAllLogGroups = jest.fn().mockResolvedValue(['a', 'b']);
|
||||||
const getDimensionValues = jest.fn().mockResolvedValue([{ label: 'e', value: 'e' }]);
|
const getDimensionValues = jest.fn().mockResolvedValue([{ label: 'e', value: 'e' }]);
|
||||||
const getEbsVolumeIds = jest.fn().mockResolvedValue([{ label: 'f', value: 'f' }]);
|
const getEbsVolumeIds = jest.fn().mockResolvedValue([{ label: 'f', value: 'f' }]);
|
||||||
const getEc2InstanceAttribute = jest.fn().mockResolvedValue([{ label: 'g', value: 'g' }]);
|
const getEc2InstanceAttribute = jest.fn().mockResolvedValue([{ label: 'g', value: 'g' }]);
|
||||||
|
@ -55,7 +55,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleLogGroupsQuery({ region, logGroupPrefix }: VariableQuery) {
|
async handleLogGroupsQuery({ region, logGroupPrefix }: VariableQuery) {
|
||||||
const logGroups = await this.datasource.describeLogGroups({ region, logGroupNamePrefix: logGroupPrefix });
|
const logGroups = await this.datasource.describeAllLogGroups({ region, logGroupNamePrefix: logGroupPrefix });
|
||||||
return logGroups.map((s) => ({
|
return logGroups.map((s) => ({
|
||||||
text: s,
|
text: s,
|
||||||
value: s,
|
value: s,
|
||||||
|
Loading…
Reference in New Issue
Block a user