Skip to content

feat(cloudwatch): add cross-account support for LogQueryWidget #33925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/aws-cdk-lib/aws-cloudwatch/lib/log-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ export interface LogQueryWidgetProps {
*/
readonly region?: string;

/**
* The account ID the log groups of this widget belong to
*
* Cross-account CloudWatch Logs queries require appropriate permissions to be set up.
*
* @default Current account
*/
readonly account?: string;

/**
* The type of view to use
*
Expand Down Expand Up @@ -123,6 +132,11 @@ export class LogQueryWidget extends ConcreteWidget {
query: `${sources} | ${query}`,
};

// Add account ID if specified for cross-account log queries
if (this.props.account) {
properties.accountId = this.props.account;
}

// adding stacked property in case of LINE or STACKEDAREA
if (this.props.view === LogQueryVisualizationType.LINE || this.props.view === LogQueryVisualizationType.STACKEDAREA) {
// assign the right native view value. both types share the same value
Expand Down
70 changes: 70 additions & 0 deletions packages/aws-cdk-lib/aws-cloudwatch/test/dashboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { App, Duration, Stack } from '../../core';
import {
Dashboard, DashboardVariable, DefaultValue,
GraphWidget,
LogQueryWidget,
MathExpression,
PeriodOverride,
TextWidget,
Expand Down Expand Up @@ -438,6 +439,75 @@ describe('Dashboard', () => {
});
});

test('log query widget supports cross-account queries', () => {
// GIVEN
const stack = new Stack();
const dashboard = new Dashboard(stack, 'Dash');

// WHEN
dashboard.addWidgets(new LogQueryWidget({
logGroupNames: ['my-log-group'],
queryString: 'fields @timestamp, @message | sort @timestamp desc | limit 20',
account: '123456789012',
width: 12,
height: 5,
}));

// THEN
const resources = Template.fromStack(stack).findResources('AWS::CloudWatch::Dashboard');
expect(Object.keys(resources).length).toEqual(1);
const key = Object.keys(resources)[0];
const dashboardBody = resources[key].Properties.DashboardBody['Fn::Join'][1].join('');
const parsedBody = JSON.parse(dashboardBody);
expect(parsedBody.widgets[0].properties.accountId).toEqual('123456789012');
});

test('log query widget without account specified does not include accountId', () => {
// GIVEN
const stack = new Stack();
const dashboard = new Dashboard(stack, 'Dash');

// WHEN
dashboard.addWidgets(new LogQueryWidget({
logGroupNames: ['my-log-group'],
queryString: 'fields @timestamp, @message | sort @timestamp desc | limit 20',
width: 12,
height: 5,
}));

// THEN
const resources = Template.fromStack(stack).findResources('AWS::CloudWatch::Dashboard');
expect(Object.keys(resources).length).toEqual(1);
const key = Object.keys(resources)[0];
const dashboardBody = resources[key].Properties.DashboardBody['Fn::Join'][1].join('');
const parsedBody = JSON.parse(dashboardBody);
expect(parsedBody.widgets[0].properties.accountId).toBeUndefined();
});

test('log query widget supports multiple log groups from different accounts', () => {
// GIVEN
const stack = new Stack();
const dashboard = new Dashboard(stack, 'Dash');

// WHEN
dashboard.addWidgets(new LogQueryWidget({
logGroupNames: ['my-log-group-1', 'my-log-group-2'],
queryString: 'fields @timestamp, @message | sort @timestamp desc | limit 20',
account: '123456789012',
width: 12,
height: 5,
}));

// THEN
const resources = Template.fromStack(stack).findResources('AWS::CloudWatch::Dashboard');
expect(Object.keys(resources).length).toEqual(1);
const key = Object.keys(resources)[0];
const dashboardBody = resources[key].Properties.DashboardBody['Fn::Join'][1].join('');
const parsedBody = JSON.parse(dashboardBody);
expect(parsedBody.widgets[0].properties.accountId).toEqual('123456789012');
expect(parsedBody.widgets[0].properties.query).toContain("SOURCE 'my-log-group-1' | SOURCE 'my-log-group-2'");
});

test('search values fail if empty dimensions', () => {
expect(() => Values.fromSearchComponents({
namespace: 'AWS/EC2',
Expand Down