Skip to content

Commit b5b03eb

Browse files
author
Nithin Venkatesh
committed
Allow root level canary scripts files for Puppeteer versions 11 and greater
1 parent c2a0ad7 commit b5b03eb

4 files changed

Lines changed: 147 additions & 4 deletions

File tree

packages/aws-cdk-lib/aws-synthetics/README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ new synthetics.Canary(this, 'Bucket Canary', {
267267
```
268268

269269
> **Note:** Synthetics have a specified folder structure for canaries.
270-
> For Node with puppeteer scripts supplied via `code.fromAsset()` or `code.fromBucket()`, the canary resource requires the following folder structure:
270+
> For Node with puppeteer scripts supplied via `code.fromAsset()` or `code.fromBucket()`, the canary resource requires the following folder structure for runtime versions lesser than `syn-nodejs-puppeteer-11.0`:
271271
>
272272
> ```plaintext
273273
> canary/
@@ -276,6 +276,22 @@ new synthetics.Canary(this, 'Bucket Canary', {
276276
> ├── <filename>.js
277277
> ```
278278
>
279+
> For puppeteer based runtime versions greater than or equal to `syn-nodejs-puppeteer-11.0`, `nodjs/node_modules` is not necessary but supported.
280+
>
281+
> Both
282+
> ```plaintext
283+
> canary/
284+
> ├── nodejs/
285+
> ├── node_modules/
286+
> ├── <filename>.js
287+
> ```
288+
> And
289+
> ```plaintext
290+
> canary/
291+
> ├── <filename>.js
292+
> ```
293+
> are supported.
294+
>
279295
> For Node with playwright scripts supplied via `code.fromAsset()` or `code.fromBucket()`, the canary resource requires the following folder structure:
280296
>
281297
> ```plaintext

packages/aws-cdk-lib/aws-synthetics/lib/code.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,21 @@ export class AssetCode extends Code {
150150
if (runtimeName && !Token.isUnresolved(runtimeName)) {
151151
const playwrightValidExtensions = ['.cjs', '.mjs', '.js'];
152152
const hasValidExtension = playwrightValidExtensions.some(ext => fs.existsSync(path.join(assetPath, `${filename}${ext}`)));
153-
// Requires asset directory to have the structure 'nodejs/node_modules' for puppeteer runtime.
154-
if (family === RuntimeFamily.NODEJS && runtimeName.includes('puppeteer') && !fs.existsSync(path.join(assetPath, 'nodejs', 'node_modules', nodeFilename))) {
155-
throw new ValidationError(`The canary resource requires that the handler is present at "nodejs/node_modules/${nodeFilename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`, scope);
153+
// Requires asset directory to have the structure 'nodejs/node_modules' for puppeteer runtime < version 11.0.
154+
if (family === RuntimeFamily.NODEJS && runtimeName.includes('puppeteer')) {
155+
const versionMatch = runtimeName.match(/syn-nodejs-puppeteer-(\d+\.\d+)/);
156+
const version = versionMatch ? parseFloat(versionMatch[1]) : 0;
157+
if (version >= 11.0) {
158+
// For versions > 11.0, allow JS files in root path
159+
if (!fs.existsSync(path.join(assetPath, nodeFilename)) && !fs.existsSync(path.join(assetPath, 'nodejs', 'node_modules', nodeFilename))) {
160+
throw new ValidationError(`The canary resource requires that the handler is present at "${nodeFilename}" or "nodejs/node_modules/${nodeFilename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`, scope);
161+
}
162+
} else {
163+
// For versions < 11.0, require nodejs/node_modules structure
164+
if (!fs.existsSync(path.join(assetPath, 'nodejs', 'node_modules', nodeFilename))) {
165+
throw new ValidationError(`The canary resource requires that the handler is present at "nodejs/node_modules/${nodeFilename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`, scope);
166+
}
167+
}
156168
}
157169
// Requires the canary handler file to have the extension '.js', '.mjs', or '.cjs' for the playwright runtime.
158170
if (family === RuntimeFamily.NODEJS && runtimeName.includes('playwright') && !hasValidExtension) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
var synthetics = require('Synthetics');
2+
const log = require('SyntheticsLogger');
3+
const https = require('https');
4+
const http = require('http');
5+
6+
const apiCanaryBlueprint = async function () {
7+
const postData = "";
8+
9+
const verifyRequest = async function (requestOption) {
10+
return new Promise((resolve, reject) => {
11+
log.info("Making request with options: " + JSON.stringify(requestOption));
12+
let req
13+
if (requestOption.protocol === 'https:') {
14+
req = https.request(requestOption);
15+
} else {
16+
req = http.request(requestOption);
17+
}
18+
req.on('response', (res) => {
19+
log.info(`Status Code: ${res.statusCode}`)
20+
log.info(`Response Headers: ${JSON.stringify(res.headers)}`)
21+
if (res.statusCode !== 200) {
22+
reject("Failed: " + requestOption.pathname);
23+
}
24+
res.on('data', (d) => {
25+
log.info("Response: " + d);
26+
});
27+
res.on('end', () => {
28+
resolve();
29+
})
30+
});
31+
32+
req.on('error', (error) => {
33+
reject(error);
34+
});
35+
36+
if (postData) {
37+
req.write(postData);
38+
}
39+
req.end();
40+
});
41+
}
42+
43+
const headers = {}
44+
headers['User-Agent'] = [synthetics.getCanaryUserAgentString(), headers['User-Agent']].join(' ');
45+
const requestOptions = new URL(process.env.URL);
46+
requestOptions['headers'] = headers;
47+
await verifyRequest(requestOptions);
48+
};
49+
50+
exports.functionName = async () => {
51+
return await apiCanaryBlueprint();
52+
};

packages/aws-cdk-lib/aws-synthetics/test/code.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,46 @@ describe(synthetics.Code.fromAsset, () => {
227227
.toThrow(`The canary resource requires that the handler is present at "nodejs/node_modules/incorrect.js" but not found at ${assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`);
228228
});
229229

230+
test('puppeteer runtime >= 11.0 allows JS files in root path', () => {
231+
// GIVEN
232+
const stack = new Stack(new App(), 'canaries');
233+
const assetPath = path.join(__dirname, 'canaries', 'puppeteer');
234+
235+
// WHEN/THEN - should not throw for puppeteer 11.0+
236+
expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_11_0.name))
237+
.not.toThrow();
238+
});
239+
240+
test('puppeteer runtime >= 11.0 also allows nodejs/node_modules structure', () => {
241+
// GIVEN
242+
const stack = new Stack(new App(), 'canaries');
243+
const assetPath = path.join(__dirname, 'canaries');
244+
245+
// WHEN/THEN - should not throw for puppeteer 11.0+ with nodejs structure (has both root canary.js and nodejs/node_modules/canary.js)
246+
expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_11_0.name))
247+
.not.toThrow();
248+
});
249+
250+
test('puppeteer runtime < 11.0 requires nodejs/node_modules structure', () => {
251+
// GIVEN
252+
const stack = new Stack(new App(), 'canaries');
253+
const assetPath = path.join(__dirname, 'canaries');
254+
255+
// WHEN/THEN - should not throw for puppeteer < 11.0 since nodejs/node_modules/canary.js exists
256+
expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_10_0.name))
257+
.not.toThrow();
258+
});
259+
260+
test('puppeteer runtime < 11.0 fails without nodejs/node_modules structure', () => {
261+
// GIVEN
262+
const stack = new Stack(new App(), 'canaries');
263+
const assetPath = path.join(__dirname, 'canaries', 'puppeteer'); // Only has root-level files
264+
265+
// WHEN/THEN - should throw for puppeteer < 11.0 without nodejs/node_modules structure
266+
expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_10_0.name))
267+
.toThrow(`The canary resource requires that the handler is present at "nodejs/node_modules/canary.js" but not found at ${assetPath}`);
268+
});
269+
230270
test('passes if bundling is specified', () => {
231271
// GIVEN
232272
const stack = new Stack(new App(), 'canaries');
@@ -253,6 +293,28 @@ describe(synthetics.Code.fromAsset, () => {
253293
.not.toThrow();
254294
});
255295

296+
test('puppeteer 11.0+ with bundling allows root-level JS files', () => {
297+
// GIVEN
298+
const stack = new Stack(new App(), 'canaries');
299+
const assetPath = path.join(__dirname, 'canaries', 'nodejs', 'node_modules');
300+
const code = synthetics.Code.fromAsset(assetPath, {
301+
bundling: {
302+
image: DockerImage.fromRegistry('dummy'),
303+
local: {
304+
tryBundle(outputDir) {
305+
// Create JS file in root for puppeteer 11.0+
306+
fs.copyFileSync(path.join(assetPath, 'canary.js'), path.join(outputDir, 'canary.js'));
307+
return true;
308+
},
309+
},
310+
},
311+
});
312+
313+
// THEN - should not throw for puppeteer 11.0+ with root-level file
314+
expect(() => code.bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_11_0.name))
315+
.not.toThrow();
316+
});
317+
256318
test('fails if bundling is specified but folder structure is wrong', () => {
257319
// GIVEN
258320
const stack = new Stack(new App(), 'canaries');
@@ -275,6 +337,7 @@ describe(synthetics.Code.fromAsset, () => {
275337
expect(() => code.bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_9_1.name))
276338
.toThrow(`The canary resource requires that the handler is present at "nodejs/node_modules/canary.js" but not found at ${assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`);
277339
});
340+
278341
});
279342

280343
describe(synthetics.Code.fromBucket, () => {

0 commit comments

Comments
 (0)