Skip to content

Commit de218ba

Browse files
authored
feat(synthetics): add Python runtime and latest Nodejs runtime (#16069)
This PR addresses the fact that the current synthetics module was built to support nodejs runtimes only by opening support for python runtimes. Closes #15138 and #16177. **Breaking Changes** - `Runtime('customRuntimeHere')` becomes `Runtime('customRuntime', 'runtimeFamily')` - `Code.fromAnything('path').bind(this, 'handler')` becomes `Code.fromAnything('path').bind(this, 'handler', 'runtimeFamily')` **Whats in this PR?** - Adds latest Nodejs runtime (`syn-nodejs-puppeteer-3.2`) and updates integ test to it. - Adds generic python script to the folder `test/canaries/python` in order to run unit & integration tests on it. - Adds new `RuntimeFamily` enum that is required by the `Runtime` object to differentiate between Python and Node. - Verifies the correct folder structure for Python runtimes (`python/<filename>.py`). - Updates readme. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent b838f95 commit de218ba

File tree

10 files changed

+471
-115
lines changed

10 files changed

+471
-115
lines changed

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ new synthetics.Canary(this, 'Bucket Canary', {
130130
});
131131
```
132132

133-
> **Note:** For `code.fromAsset()` and `code.fromBucket()`, the canary resource requires the following folder structure:
133+
> **Note:** Synthetics have a specified folder structure for canaries. For Node scripts supplied via `code.fromAsset()` or `code.fromBucket()`, the canary resource requires the following folder structure:
134134
>
135135
> ```plaintext
136136
> canary/
@@ -139,6 +139,15 @@ new synthetics.Canary(this, 'Bucket Canary', {
139139
> ├── <filename>.js
140140
> ```
141141
>
142+
>
143+
> For Python scripts supplied via `code.fromAsset()` or `code.fromBucket()`, the canary resource requires the following folder structure:
144+
>
145+
> ```plaintext
146+
> canary/
147+
> ├── python/
148+
> ├── <filename>.py
149+
> ```
150+
>
142151
> See Synthetics [docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html).
143152
144153
### Alarms

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

+2-76
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as s3 from '@aws-cdk/aws-s3';
55
import * as cdk from '@aws-cdk/core';
66
import { Construct } from 'constructs';
77
import { Code } from './code';
8+
import { Runtime } from './runtime';
89
import { Schedule } from './schedule';
910
import { CloudWatchSyntheticsMetrics } from './synthetics-canned-metrics.generated';
1011
import { CfnCanary } from './synthetics.generated';
@@ -64,81 +65,6 @@ export interface CustomTestOptions {
6465
readonly handler: string,
6566
}
6667

67-
/**
68-
* Runtime options for a canary
69-
*/
70-
export class Runtime {
71-
/**
72-
* `syn-1.0` includes the following:
73-
*
74-
* - Synthetics library 1.0
75-
* - Synthetics handler code 1.0
76-
* - Lambda runtime Node.js 10.x
77-
* - Puppeteer-core version 1.14.0
78-
* - The Chromium version that matches Puppeteer-core 1.14.0
79-
*
80-
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-1.0
81-
*/
82-
public static readonly SYNTHETICS_1_0 = new Runtime('syn-1.0');
83-
84-
/**
85-
* `syn-nodejs-2.0` includes the following:
86-
* - Lambda runtime Node.js 10.x
87-
* - Puppeteer-core version 3.3.0
88-
* - Chromium version 83.0.4103.0
89-
*
90-
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-2.0
91-
*/
92-
public static readonly SYNTHETICS_NODEJS_2_0 = new Runtime('syn-nodejs-2.0');
93-
94-
95-
/**
96-
* `syn-nodejs-2.1` includes the following:
97-
* - Lambda runtime Node.js 10.x
98-
* - Puppeteer-core version 3.3.0
99-
* - Chromium version 83.0.4103.0
100-
*
101-
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-2.1
102-
*/
103-
public static readonly SYNTHETICS_NODEJS_2_1 = new Runtime('syn-nodejs-2.1');
104-
105-
/**
106-
* `syn-nodejs-2.2` includes the following:
107-
* - Lambda runtime Node.js 10.x
108-
* - Puppeteer-core version 3.3.0
109-
* - Chromium version 83.0.4103.0
110-
*
111-
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-2.2
112-
*/
113-
public static readonly SYNTHETICS_NODEJS_2_2 = new Runtime('syn-nodejs-2.2');
114-
115-
/**
116-
* `syn-nodejs-puppeteer-3.0` includes the following:
117-
* - Lambda runtime Node.js 12.x
118-
* - Puppeteer-core version 5.5.0
119-
* - Chromium version 88.0.4298.0
120-
*
121-
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-nodejs-puppeteer-3.0
122-
*/
123-
public static readonly SYNTHETICS_NODEJS_PUPPETEER_3_0 = new Runtime('syn-nodejs-puppeteer-3.0');
124-
125-
/**
126-
* `syn-nodejs-puppeteer-3.1` includes the following:
127-
* - Lambda runtime Node.js 12.x
128-
* - Puppeteer-core version 5.5.0
129-
* - Chromium version 88.0.4298.0
130-
*
131-
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-nodejs-puppeteer-3.1
132-
*/
133-
public static readonly SYNTHETICS_NODEJS_PUPPETEER_3_1 = new Runtime('syn-nodejs-puppeteer-3.1');
134-
135-
/**
136-
* @param name The name of the runtime version
137-
*/
138-
public constructor(public readonly name: string) {
139-
}
140-
}
141-
14268
/**
14369
* Options for specifying the s3 location that stores the data of each canary run. The artifacts bucket location **cannot**
14470
* be updated once the canary is created.
@@ -398,7 +324,7 @@ export class Canary extends cdk.Resource {
398324
private createCode(props: CanaryProps): CfnCanary.CodeProperty {
399325
const codeConfig = {
400326
handler: props.test.handler,
401-
...props.test.code.bind(this, props.test.handler),
327+
...props.test.code.bind(this, props.test.handler, props.runtime.family),
402328
};
403329
return {
404330
handler: codeConfig.handler,

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

+16-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import * as fs from 'fs';
22
import * as path from 'path';
33
import * as s3 from '@aws-cdk/aws-s3';
44
import * as s3_assets from '@aws-cdk/aws-s3-assets';
5-
import { Construct } from '@aws-cdk/core';
5+
import { Construct } from 'constructs';
6+
import { RuntimeFamily } from './runtime';
67

78
/**
89
* The code the canary should execute
@@ -56,7 +57,7 @@ export abstract class Code {
5657
*
5758
* @returns a bound `CodeConfig`.
5859
*/
59-
public abstract bind(scope: Construct, handler: string): CodeConfig;
60+
public abstract bind(scope: Construct, handler: string, family: RuntimeFamily): CodeConfig;
6061
}
6162

6263
/**
@@ -95,8 +96,8 @@ export class AssetCode extends Code {
9596
}
9697
}
9798

98-
public bind(scope: Construct, handler: string): CodeConfig {
99-
this.validateCanaryAsset(handler);
99+
public bind(scope: Construct, handler: string, family: RuntimeFamily): CodeConfig {
100+
this.validateCanaryAsset(handler, family);
100101

101102
// If the same AssetCode is used multiple times, retain only the first instantiation.
102103
if (!this.asset) {
@@ -126,14 +127,19 @@ export class AssetCode extends Code {
126127
*
127128
* @param handler the canary handler
128129
*/
129-
private validateCanaryAsset(handler: string) {
130+
private validateCanaryAsset(handler: string, family: RuntimeFamily) {
130131
if (path.extname(this.assetPath) !== '.zip') {
131132
if (!fs.lstatSync(this.assetPath).isDirectory()) {
132133
throw new Error(`Asset must be a .zip file or a directory (${this.assetPath})`);
133134
}
134-
const filename = `${handler.split('.')[0]}.js`;
135-
if (!fs.existsSync(path.join(this.assetPath, 'nodejs', 'node_modules', filename))) {
136-
throw new Error(`The canary resource requires that the handler is present at "nodejs/node_modules/${filename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html#CloudWatch_Synthetics_Canaries_write_from_scratch)`);
135+
const filename = handler.split('.')[0];
136+
const nodeFilename = `${filename}.js`;
137+
const pythonFilename = `${filename}.py`;
138+
if (family === RuntimeFamily.NODEJS && !fs.existsSync(path.join(this.assetPath, 'nodejs', 'node_modules', nodeFilename))) {
139+
throw new Error(`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)`);
140+
}
141+
if (family === RuntimeFamily.PYTHON && !fs.existsSync(path.join(this.assetPath, 'python', pythonFilename))) {
142+
throw new Error(`The canary resource requires that the handler is present at "python/${pythonFilename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Python.html)`);
137143
}
138144
}
139145
}
@@ -151,7 +157,7 @@ export class InlineCode extends Code {
151157
}
152158
}
153159

154-
public bind(_scope: Construct, handler: string): CodeConfig {
160+
public bind(_scope: Construct, handler: string, _family: RuntimeFamily): CodeConfig {
155161

156162
if (handler !== 'index.handler') {
157163
throw new Error(`The handler for inline code must be "index.handler" (got "${handler}")`);
@@ -171,7 +177,7 @@ export class S3Code extends Code {
171177
super();
172178
}
173179

174-
public bind(_scope: Construct, _handler: string): CodeConfig {
180+
public bind(_scope: Construct, _handler: string, _family: RuntimeFamily): CodeConfig {
175181
return {
176182
s3Location: {
177183
bucketName: this.bucket.bucketName,

packages/@aws-cdk/aws-synthetics/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './canary';
22
export * from './code';
3+
export * from './runtime';
34
export * from './schedule';
45

56
// AWS::Synthetics CloudFormation Resources:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* All known Lambda runtime families.
3+
*/
4+
export enum RuntimeFamily {
5+
/**
6+
* All Lambda runtimes that depend on Node.js.
7+
*/
8+
NODEJS,
9+
10+
/**
11+
* All lambda runtimes that depend on Python.
12+
*/
13+
PYTHON,
14+
15+
/**
16+
* Any future runtime family.
17+
*/
18+
OTHER,
19+
}
20+
21+
/**
22+
* Runtime options for a canary
23+
*/
24+
export class Runtime {
25+
/**
26+
* **Deprecated by AWS Synthetics. You can't create canaries with deprecated runtimes.**
27+
*
28+
* `syn-1.0` includes the following:
29+
*
30+
* - Synthetics library 1.0
31+
* - Synthetics handler code 1.0
32+
* - Lambda runtime Node.js 10.x
33+
* - Puppeteer-core version 1.14.0
34+
* - The Chromium version that matches Puppeteer-core 1.14.0
35+
*
36+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-1.0
37+
*/
38+
public static readonly SYNTHETICS_1_0 = new Runtime('syn-1.0', RuntimeFamily.NODEJS);
39+
40+
/**
41+
* **Deprecated by AWS Synthetics. You can't create canaries with deprecated runtimes.**
42+
*
43+
* `syn-nodejs-2.0` includes the following:
44+
* - Lambda runtime Node.js 10.x
45+
* - Puppeteer-core version 3.3.0
46+
* - Chromium version 83.0.4103.0
47+
*
48+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-2.0
49+
*/
50+
public static readonly SYNTHETICS_NODEJS_2_0 = new Runtime('syn-nodejs-2.0', RuntimeFamily.NODEJS);
51+
52+
53+
/**
54+
* **Deprecated by AWS Synthetics. You can't create canaries with deprecated runtimes.**
55+
*
56+
* `syn-nodejs-2.1` includes the following:
57+
* - Lambda runtime Node.js 10.x
58+
* - Puppeteer-core version 3.3.0
59+
* - Chromium version 83.0.4103.0
60+
*
61+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-2.1
62+
*/
63+
public static readonly SYNTHETICS_NODEJS_2_1 = new Runtime('syn-nodejs-2.1', RuntimeFamily.NODEJS);
64+
65+
/**
66+
* **Deprecated by AWS Synthetics. You can't create canaries with deprecated runtimes.**
67+
*
68+
* `syn-nodejs-2.2` includes the following:
69+
* - Lambda runtime Node.js 10.x
70+
* - Puppeteer-core version 3.3.0
71+
* - Chromium version 83.0.4103.0
72+
*
73+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-2.2
74+
*/
75+
public static readonly SYNTHETICS_NODEJS_2_2 = new Runtime('syn-nodejs-2.2', RuntimeFamily.NODEJS);
76+
77+
/**
78+
* `syn-nodejs-puppeteer-3.0` includes the following:
79+
* - Lambda runtime Node.js 12.x
80+
* - Puppeteer-core version 5.5.0
81+
* - Chromium version 88.0.4298.0
82+
*
83+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-nodejs-puppeteer-3.0
84+
*/
85+
public static readonly SYNTHETICS_NODEJS_PUPPETEER_3_0 = new Runtime('syn-nodejs-puppeteer-3.0', RuntimeFamily.NODEJS);
86+
87+
/**
88+
* `syn-nodejs-puppeteer-3.1` includes the following:
89+
* - Lambda runtime Node.js 12.x
90+
* - Puppeteer-core version 5.5.0
91+
* - Chromium version 88.0.4298.0
92+
*
93+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-nodejs-puppeteer-3.1
94+
*/
95+
public static readonly SYNTHETICS_NODEJS_PUPPETEER_3_1 = new Runtime('syn-nodejs-puppeteer-3.1', RuntimeFamily.NODEJS);
96+
97+
/**
98+
* `syn-nodejs-puppeteer-3.2` includes the following:
99+
* - Lambda runtime Node.js 12.x
100+
* - Puppeteer-core version 5.5.0
101+
* - Chromium version 88.0.4298.0
102+
*
103+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-nodejs-puppeteer-3.2
104+
*/
105+
public static readonly SYNTHETICS_NODEJS_PUPPETEER_3_2 = new Runtime('syn-nodejs-puppeteer-3.2', RuntimeFamily.NODEJS);
106+
107+
/**
108+
* `syn-python-selenium-1.0` includes the following:
109+
* - Lambda runtime Python 3.8
110+
* - Selenium version 3.141.0
111+
* - Chromium version 83.0.4103.0
112+
*
113+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_python_selenium.html
114+
*/
115+
public static readonly SYNTHETICS_PYTHON_SELENIUM_1_0 = new Runtime('syn-python-selenium-1.0', RuntimeFamily.PYTHON);
116+
117+
/**
118+
* @param name The name of the runtime version
119+
* @param family The Lambda runtime family
120+
*/
121+
public constructor(public readonly name: string, public readonly family: RuntimeFamily) {
122+
}
123+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# This example comes from the AWS Synthetics service console "API canary" blueprint
2+
3+
import json
4+
import http.client
5+
import urllib.parse
6+
from aws_synthetics.selenium import synthetics_webdriver as syn_webdriver
7+
from aws_synthetics.common import synthetics_logger as logger
8+
9+
10+
def verify_request(method, url, post_data=None, headers={}):
11+
parsed_url = urllib.parse.urlparse(url)
12+
user_agent = str(syn_webdriver.get_canary_user_agent_string())
13+
if "User-Agent" in headers:
14+
headers["User-Agent"] = " ".join([user_agent, headers["User-Agent"]])
15+
else:
16+
headers["User-Agent"] = "{}".format(user_agent)
17+
18+
logger.info("Making request with Method: '%s' URL: %s: Data: %s Headers: %s" % (
19+
method, url, json.dumps(post_data), json.dumps(headers)))
20+
21+
if parsed_url.scheme == "https":
22+
conn = http.client.HTTPSConnection(parsed_url.hostname, parsed_url.port)
23+
else:
24+
conn = http.client.HTTPConnection(parsed_url.hostname, parsed_url.port)
25+
26+
conn.request(method, url, str(post_data), headers)
27+
response = conn.getresponse()
28+
logger.info("Status Code: %s " % response.status)
29+
logger.info("Response Headers: %s" % json.dumps(response.headers.as_string()))
30+
31+
if not response.status or response.status < 200 or response.status > 299:
32+
try:
33+
logger.error("Response: %s" % response.read().decode())
34+
finally:
35+
if response.reason:
36+
conn.close()
37+
raise Exception("Failed: %s" % response.reason)
38+
else:
39+
conn.close()
40+
raise Exception("Failed with status code: %s" % response.status)
41+
42+
logger.info("Response: %s" % response.read().decode())
43+
logger.info("HTTP request successfully executed")
44+
conn.close()
45+
46+
47+
def main():
48+
49+
url = 'https://example.com/'
50+
method = 'GET'
51+
postData = ""
52+
headers = {}
53+
54+
verify_request(method, url, None, headers)
55+
56+
logger.info("Canary successfully executed")
57+
58+
59+
def handler(event, context):
60+
logger.info("Selenium Python API canary")
61+
main()

0 commit comments

Comments
 (0)