Skip to content

Commit 1eaec92

Browse files
authored
fix(synthetics): updated handler validation (#26569)
This fix updates handler validation based on Synthetic rules for [Node](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html#:~:text=and%20files.-,Handler%20name,-Be%20sure%20to) and [Python](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Python.html#:~:text=Packaging%20your%20canary%20files) runtimes. Closes #26540. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 6a70b4f commit 1eaec92

File tree

18 files changed

+15510
-12583
lines changed

18 files changed

+15510
-12583
lines changed

packages/@aws-cdk/aws-synthetics-alpha/lib/canary.ts

+30-16
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,9 @@ export class Test {
2222
* @param options The configuration options
2323
*/
2424
public static custom(options: CustomTestOptions): Test {
25-
Test.validateHandler(options.handler);
2625
return new Test(options.code, options.handler);
2726
}
2827

29-
/**
30-
* Verifies that the given handler ends in '.handler'. Returns the handler if successful and
31-
* throws an error if not.
32-
*
33-
* @param handler - the handler given by the user
34-
*/
35-
private static validateHandler(handler: string) {
36-
if (!handler.endsWith('.handler')) {
37-
throw new Error(`Canary Handler must end in '.handler' (${handler})`);
38-
}
39-
if (handler.length > 21) {
40-
throw new Error(`Canary Handler must be less than 21 characters (${handler})`);
41-
}
42-
}
43-
4428
/**
4529
* Construct a Test property
4630
*
@@ -422,6 +406,7 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
422406
* Returns the code object taken in by the canary resource.
423407
*/
424408
private createCode(props: CanaryProps): CfnCanary.CodeProperty {
409+
this.validateHandler(props.test.handler, props.runtime);
425410
const codeConfig = {
426411
handler: props.test.handler,
427412
...props.test.code.bind(this, props.test.handler, props.runtime.family),
@@ -435,6 +420,35 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
435420
};
436421
}
437422

423+
/**
424+
* Verifies that the handler name matches the conventions given a certain runtime.
425+
*
426+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-code.html#cfn-synthetics-canary-code-handler
427+
* @param handler - the name of the handler
428+
* @param runtime - the runtime version
429+
*/
430+
private validateHandler(handler: string, runtime: Runtime) {
431+
const oldRuntimes = [
432+
Runtime.SYNTHETICS_PYTHON_SELENIUM_1_0,
433+
Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_0,
434+
Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1,
435+
Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_2,
436+
Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_3,
437+
];
438+
if (oldRuntimes.includes(runtime)) {
439+
if (!handler.match(/^[0-9A-Za-z_\\-]+\.handler*$/)) {
440+
throw new Error(`Canary Handler must be specified as \'fileName.handler\' for legacy runtimes, received ${handler}`);
441+
}
442+
} else {
443+
if (!handler.match(/^([0-9a-zA-Z_-]+\/)*[0-9A-Za-z_\\-]+\.[A-Za-z_][A-Za-z0-9_]*$/)) {
444+
throw new Error(`Canary Handler must be specified either as \'fileName.handler\', \'fileName.functionName\', or \'folder/fileName.functionName\', received ${handler}`);
445+
}
446+
}
447+
if (handler.length < 1 || handler.length > 128) {
448+
throw new Error(`Canary Handler length must be between 1 and 128, received ${handler.length}`);
449+
}
450+
}
451+
438452
private createRunConfig(props: CanaryProps): CfnCanary.RunConfigProperty | undefined {
439453
if (!props.environmentVariables) {
440454
return undefined;

packages/@aws-cdk/aws-synthetics-alpha/test/canaries/nodejs/node_modules/folder/canary.js

+52
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-synthetics-alpha/test/canary.test.ts

+87
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as path from 'path';
12
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
23
import { Match, Template } from 'aws-cdk-lib/assertions';
34
import * as ec2 from 'aws-cdk-lib/aws-ec2';
@@ -703,3 +704,89 @@ testDeprecated('Role policy generated as expected', () => {
703704
}],
704705
});
705706
});
707+
708+
testDeprecated('Should create handler with path for recent runtimes', () => {
709+
// GIVEN
710+
const stack = new Stack();
711+
712+
// WHEN
713+
new synthetics.Canary(stack, 'Canary', {
714+
canaryName: 'mycanary',
715+
test: synthetics.Test.custom({
716+
handler: 'folder/canary.functionName',
717+
code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')),
718+
}),
719+
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_8,
720+
});
721+
722+
// THEN
723+
Template.fromStack(stack).hasResourceProperties('AWS::Synthetics::Canary', {
724+
Name: 'mycanary',
725+
Code: {
726+
Handler: 'folder/canary.functionName',
727+
},
728+
RuntimeVersion: 'syn-nodejs-puppeteer-3.8',
729+
});
730+
});
731+
732+
describe('handler validation', () => {
733+
testDeprecated('legacy runtimes', () => {
734+
const stack = new Stack();
735+
expect(() => {
736+
new synthetics.Canary(stack, 'Canary', {
737+
test: synthetics.Test.custom({
738+
handler: 'index.functionName',
739+
code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')),
740+
}),
741+
runtime: synthetics.Runtime.SYNTHETICS_PYTHON_SELENIUM_1_0,
742+
});
743+
}).toThrow(/Canary Handler must be specified as 'fileName.handler' for legacy runtimes/);
744+
});
745+
746+
testDeprecated('recent runtimes', () => {
747+
const stack = new Stack();
748+
749+
expect(() => {
750+
new synthetics.Canary(stack, 'Canary', {
751+
test: synthetics.Test.custom({
752+
handler: 'invalidHandler',
753+
code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')),
754+
}),
755+
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_9,
756+
});
757+
}).toThrow(/Canary Handler must be specified either as 'fileName.handler', 'fileName.functionName', or 'folder\/fileName.functionName'/);
758+
759+
expect(() => {
760+
new synthetics.Canary(stack, 'Canary1', {
761+
test: synthetics.Test.custom({
762+
handler: 'canary.functionName',
763+
code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')),
764+
}),
765+
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_9,
766+
});
767+
}).not.toThrow();
768+
769+
expect(() => {
770+
new synthetics.Canary(stack, 'Canary2', {
771+
test: synthetics.Test.custom({
772+
handler: 'folder/canary.functionName',
773+
code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')),
774+
}),
775+
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_9,
776+
});
777+
}).not.toThrow();
778+
});
779+
780+
testDeprecated('handler length', () => {
781+
const stack = new Stack();
782+
expect(() => {
783+
new synthetics.Canary(stack, 'Canary1', {
784+
test: synthetics.Test.custom({
785+
handler: 'longHandlerName'.repeat(10) + '.handler',
786+
code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')),
787+
}),
788+
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_9,
789+
});
790+
}).toThrow(/Canary Handler length must be between 1 and 128/);
791+
});
792+
});

packages/@aws-cdk/aws-synthetics-alpha/test/integ.canary.js.snapshot/IntegCanaryTestDefaultTestDeployAssert3AD5A094.assets.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
{
2-
"version": "32.0.0",
2+
"version": "33.0.0",
33
"files": {
4-
"fe2373df587bda7e4bf68910b7be30b2042493413a6f6f2c43efab4f184d3bad": {
4+
"d5b88712f80b7d8c96ddbffc883a569f5e5681cf14a985d4d0933ca3712f5110": {
55
"source": {
6-
"path": "asset.fe2373df587bda7e4bf68910b7be30b2042493413a6f6f2c43efab4f184d3bad.bundle",
6+
"path": "asset.d5b88712f80b7d8c96ddbffc883a569f5e5681cf14a985d4d0933ca3712f5110.bundle",
77
"packaging": "zip"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "fe2373df587bda7e4bf68910b7be30b2042493413a6f6f2c43efab4f184d3bad.zip",
12+
"objectKey": "d5b88712f80b7d8c96ddbffc883a569f5e5681cf14a985d4d0933ca3712f5110.zip",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}
1616
},
17-
"5c382b4e425866a69d59a5cffba84fb128c5482ea37a4da5b0ddb7a5a803d15e": {
17+
"df5ea21aeb8a2b4d703bce966a4cf197c3a558b9b1bd275879fa58d12447002e": {
1818
"source": {
1919
"path": "IntegCanaryTestDefaultTestDeployAssert3AD5A094.template.json",
2020
"packaging": "file"
2121
},
2222
"destinations": {
2323
"current_account-current_region": {
2424
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
25-
"objectKey": "5c382b4e425866a69d59a5cffba84fb128c5482ea37a4da5b0ddb7a5a803d15e.json",
25+
"objectKey": "df5ea21aeb8a2b4d703bce966a4cf197c3a558b9b1bd275879fa58d12447002e.json",
2626
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
2727
}
2828
}

0 commit comments

Comments
 (0)