Skip to content

Commit

Permalink
chore(toolkit): introduce cloudwatchlogsmonitor to deploy and watch (#…
Browse files Browse the repository at this point in the history
…33219)

### Issue # (if applicable)

Closes #<issue number here>.

### Reason for this change



### Description of changes



### Describe any new or updated permissions being added




### Description of how you validated changes



### Checklist
- [ ] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
kaizencc authored Jan 30, 2025
1 parent a486bbc commit 71c492a
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 34 deletions.
16 changes: 8 additions & 8 deletions packages/@aws-cdk/toolkit/lib/actions/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ export interface BaseDeployOptions {
* @default 1
*/
readonly concurrency?: number;

/**
* Whether to send logs from all CloudWatch log groups in the template
* to the IoHost
*
* @default - false
*/
readonly traceLogs?: boolean;
}

export interface DeployOptions extends BaseDeployOptions {
Expand Down Expand Up @@ -205,14 +213,6 @@ export interface DeployOptions extends BaseDeployOptions {
*/
readonly outputsFile?: string;

/**
* Whether to show logs from all CloudWatch log groups in the template
* locally in the users terminal
*
* @default - false
*/
readonly traceLogs?: boolean;

/**
* Build/publish assets for a single stack in parallel
*
Expand Down
9 changes: 9 additions & 0 deletions packages/@aws-cdk/toolkit/lib/actions/deploy/private/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DeployOptions } from '..';
import { CloudWatchLogEventMonitor } from '../../../api/aws-cdk';

export * from './helpers';

Expand All @@ -14,4 +15,12 @@ export interface ExtendedDeployOptions extends DeployOptions {
* @default - nothing extra is appended to the User-Agent header
*/
readonly extraUserAgent?: string;

/**
* Allows adding CloudWatch log groups to the log monitor via
* cloudWatchLogMonitor.setLogGroups();
*
* @default - not monitoring CloudWatch logs
*/
readonly cloudWatchLogMonitor?: CloudWatchLogEventMonitor;
}
8 changes: 0 additions & 8 deletions packages/@aws-cdk/toolkit/lib/actions/watch/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import type { BaseDeployOptions, HotswapMode } from '../deploy';

export interface WatchOptions extends BaseDeployOptions {
/**
* Whether to show CloudWatch logs for hotswapped resources
* locally in the users terminal
*
* @default - false
*/
readonly traceLogs?: boolean;

/**
* The extra string to append to the User-Agent header when performing AWS SDK calls.
*
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/toolkit/lib/api/aws-cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export { WorkGraph } from '../../../../aws-cdk/lib/util/work-graph';
export type { Concurrency } from '../../../../aws-cdk/lib/util/work-graph';
export { WorkGraphBuilder } from '../../../../aws-cdk/lib/util/work-graph-builder';
export type { AssetBuildNode, AssetPublishNode, StackNode } from '../../../../aws-cdk/lib/util/work-graph-types';
export { CloudWatchLogEventMonitor } from '../../../../aws-cdk/lib/api/logs/logs-monitor';
export { findCloudWatchLogGroups } from '../../../../aws-cdk/lib/api/logs/find-cloudwatch-logs';
export { HotswapPropertyOverrides, EcsHotswapProperties } from '../../../../aws-cdk/lib/api/hotswap/common';

// @todo Cloud Assembly and Executable - this is a messy API right now
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/toolkit/lib/api/io/private/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const CODES = {
// Toolkit Info codes
CDK_TOOLKIT_I0001: 'Display stack data',
CDK_TOOLKIT_I0002: 'Successfully deployed stacks',
CDK_TOOLKIT_I3001: 'Informs about any log groups that are traced as part of the deployment',
CDK_TOOLKIT_I5001: 'Display synthesis times',
CDK_TOOLKIT_I5050: 'Confirm rollback',
CDK_TOOLKIT_I5060: 'Confirm deploy security sensitive changes',
Expand Down
36 changes: 19 additions & 17 deletions packages/@aws-cdk/toolkit/lib/toolkit/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { type RollbackOptions } from '../actions/rollback';
import { type SynthOptions } from '../actions/synth';
import { patternsArrayForWatch, WatchOptions } from '../actions/watch';
import { type SdkOptions } from '../api/aws-auth';
import { DEFAULT_TOOLKIT_STACK_NAME, SdkProvider, SuccessfulDeployStackResult, StackCollection, Deployments, HotswapMode, StackActivityProgress, ResourceMigrator, obscureTemplate, serializeStructure, tagsForStack, CliIoHost, validateSnsTopicArn, Concurrency, WorkGraphBuilder, AssetBuildNode, AssetPublishNode, StackNode, formatErrorMessage } from '../api/aws-cdk';
import { DEFAULT_TOOLKIT_STACK_NAME, SdkProvider, SuccessfulDeployStackResult, StackCollection, Deployments, HotswapMode, StackActivityProgress, ResourceMigrator, obscureTemplate, serializeStructure, tagsForStack, CliIoHost, validateSnsTopicArn, Concurrency, WorkGraphBuilder, AssetBuildNode, AssetPublishNode, StackNode, formatErrorMessage, CloudWatchLogEventMonitor, findCloudWatchLogGroups } from '../api/aws-cdk';
import { CachedCloudAssemblySource, IdentityCloudAssemblySource, StackAssembly, ICloudAssemblySource, StackSelectionStrategy } from '../api/cloud-assembly';
import { ALL_STACKS, CloudAssemblySourceBuilder } from '../api/cloud-assembly/private';
import { ToolkitError } from '../api/errors';
Expand Down Expand Up @@ -447,15 +447,17 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
[`❌ ${chalk.bold(stack.stackName)} failed:`, ...(e.name ? [`${e.name}:`] : []), e.message].join(' '),
);
} finally {
// @todo
// if (options.cloudWatchLogMonitor) {
// const foundLogGroupsResult = await findCloudWatchLogGroups(await this.sdkProvider('deploy'), stack);
// options.cloudWatchLogMonitor.addLogGroups(
// foundLogGroupsResult.env,
// foundLogGroupsResult.sdk,
// foundLogGroupsResult.logGroupNames,
// );
// }
if (options.traceLogs) {
// deploy calls that originate from watch will come with their own cloudWatchLogMonitor
const cloudWatchLogMonitor = options.cloudWatchLogMonitor ?? new CloudWatchLogEventMonitor();
const foundLogGroupsResult = await findCloudWatchLogGroups(await this.sdkProvider('deploy'), stack);
cloudWatchLogMonitor.addLogGroups(
foundLogGroupsResult.env,
foundLogGroupsResult.sdk,
foundLogGroupsResult.logGroupNames,
);
await ioHost.notify(info(`The following log groups are added: ${foundLogGroupsResult.logGroupNames}`, 'CDK_TOOLKIT_I3001'));
}

// If an outputs file has been specified, create the file path and write stack outputs to it once.
// Outputs are written after all stacks have been deployed. If a stack deployment fails,
Expand Down Expand Up @@ -559,13 +561,12 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
// -------------- -------- 'cdk deploy' done -------------- 'cdk deploy' done --------------
let latch: 'pre-ready' | 'open' | 'deploying' | 'queued' = 'pre-ready';

// @todo
// const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor() : undefined;
const cloudWatchLogMonitor = options.traceLogs ? new CloudWatchLogEventMonitor() : undefined;
const deployAndWatch = async () => {
latch = 'deploying';
// cloudWatchLogMonitor?.deactivate();
cloudWatchLogMonitor?.deactivate();

await this.invokeDeployFromWatch(assembly, options);
await this.invokeDeployFromWatch(assembly, options, cloudWatchLogMonitor);

// If latch is still 'deploying' after the 'await', that's fine,
// but if it's 'queued', that means we need to deploy again
Expand All @@ -574,10 +575,10 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
// and thinks the above 'while' condition is always 'false' without the cast
latch = 'deploying';
await ioHost.notify(info("Detected file changes during deployment. Invoking 'cdk deploy' again"));
await this.invokeDeployFromWatch(assembly, options);
await this.invokeDeployFromWatch(assembly, options, cloudWatchLogMonitor);
}
latch = 'open';
// cloudWatchLogMonitor?.activate();
cloudWatchLogMonitor?.activate();
};

chokidar
Expand Down Expand Up @@ -760,13 +761,14 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
private async invokeDeployFromWatch(
assembly: StackAssembly,
options: WatchOptions,
cloudWatchLogMonitor?: CloudWatchLogEventMonitor,
): Promise<void> {
// watch defaults hotswap to enabled
const hotswap = options.hotswap ?? HotswapMode.HOTSWAP_ONLY;
const deployOptions: ExtendedDeployOptions = {
...options,
requireApproval: RequireApproval.NEVER,
// cloudWatchLogMonitor,
cloudWatchLogMonitor,
hotswap,
extraUserAgent: `cdk-watch/hotswap-${hotswap === HotswapMode.FULL_DEPLOYMENT ? 'off' : 'on'}`,
};
Expand Down
26 changes: 26 additions & 0 deletions packages/@aws-cdk/toolkit/test/actions/deploy.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
let mockFindCloudWatchLogGroups = jest.fn();

import { RequireApproval, StackParameters } from '../../lib';
import { Toolkit } from '../../lib/toolkit';
import { builderFixture, TestIoHost } from '../_helpers';
import { MockSdk } from '../util/aws-cdk';

const sdk = new MockSdk();
const ioHost = new TestIoHost();
const toolkit = new Toolkit({ ioHost });
const rollbackSpy = jest.spyOn(toolkit as any, '_rollback').mockResolvedValue({});
Expand All @@ -22,13 +26,19 @@ jest.mock('../../lib/api/aws-cdk', () => {
isSingleAssetPublished: jest.fn().mockResolvedValue(true),
readCurrentTemplate: jest.fn().mockResolvedValue({ Resources: {} }),
})),
findCloudWatchLogGroups: mockFindCloudWatchLogGroups,
};
});

beforeEach(() => {
ioHost.notifySpy.mockClear();
ioHost.requestSpy.mockClear();
jest.clearAllMocks();
mockFindCloudWatchLogGroups.mockReturnValue({
env: { name: 'Z', account: 'X', region: 'Y' },
sdk,
logGroupNames: ['/aws/lambda/lambda-function-name'],
});
});

describe('deploy', () => {
Expand Down Expand Up @@ -127,6 +137,22 @@ describe('deploy', () => {
successfulDeployment();
});

test('can trace logs', async () => {
// WHEN
const cx = await builderFixture(toolkit, 'stack-with-role');
await toolkit.deploy(cx, {
traceLogs: true,
});

// THEN
expect(ioHost.notifySpy).toHaveBeenCalledWith(expect.objectContaining({
action: 'deploy',
level: 'info',
code: 'CDK_TOOLKIT_I3001',
message: expect.stringContaining('The following log groups are added: /aws/lambda/lambda-function-name'),
}));
});

test('non sns notification arn results in error', async () => {
// WHEN
const arn = 'arn:aws:sqs:us-east-1:1111111111:resource';
Expand Down
21 changes: 20 additions & 1 deletion packages/@aws-cdk/toolkit/test/actions/watch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,39 @@ describe('watch', () => {
}));
});

test('can trace logs', async () => {
// GIVEN
const cx = await builderFixture(toolkit, 'stack-with-role');
ioHost.level = 'debug';
await toolkit.watch(cx, {
include: [],
traceLogs: true,
});

// WHEN
await fakeChokidarWatcherOn.readyCallback();

// THEN
expect(deploySpy).toHaveBeenCalledWith(expect.anything(), 'watch', expect.objectContaining({
cloudWatchLogMonitor: expect.anything(), // Not undefined
}));
});

describe.each([
[HotswapMode.FALL_BACK, 'on'],
[HotswapMode.HOTSWAP_ONLY, 'on'],
[HotswapMode.FULL_DEPLOYMENT, 'off'],
])('%p mode', (hotswapMode, userAgent) => {
test('passes through the correct hotswap mode to deployStack()', async () => {
// WHEN
// GIVEN
const cx = await builderFixture(toolkit, 'stack-with-role');
ioHost.level = 'warn';
await toolkit.watch(cx, {
include: [],
hotswap: hotswapMode,
});

// WHEN
await fakeChokidarWatcherOn.readyCallback();

// THEN
Expand Down
3 changes: 3 additions & 0 deletions packages/@aws-cdk/toolkit/test/util/aws-cdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* eslint-disable import/no-restricted-paths */

export { MockSdk } from '../../../../aws-cdk/test/util/mock-sdk';

0 comments on commit 71c492a

Please sign in to comment.