Skip to content

Commit df9f13f

Browse files
authored
feat(synthetics): support canary environment variables (#15082)
Add support for canary environment variables that will be threaded to the underlying Lambda function. This allows multiple canaries to use the same source code by extracting configuration to the resource specification. Also makes the README snippets compile since it was hard to tell whether my changes were correct. closes #10515 refer #9300 Co-authored-by: Florian Chazal <[email protected]> ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent dc3cf13 commit df9f13f

File tree

5 files changed

+140
-42
lines changed

5 files changed

+140
-42
lines changed

Diff for: packages/@aws-cdk/aws-synthetics/README.md

+23-21
Original file line numberDiff line numberDiff line change
@@ -36,40 +36,39 @@ The Hitchhikers Guide to the Galaxy
3636
The below code defines a canary that will hit the `books/topbook` endpoint every 5 minutes:
3737

3838
```ts
39-
import * as synthetics from '@aws-cdk/aws-synthetics';
40-
4139
const canary = new synthetics.Canary(this, 'MyCanary', {
4240
schedule: synthetics.Schedule.rate(Duration.minutes(5)),
43-
test: Test.custom({
41+
test: synthetics.Test.custom({
4442
code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')),
4543
handler: 'index.handler',
4644
}),
4745
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1,
46+
environmentVariables: {
47+
stage: 'prod',
48+
},
4849
});
4950
```
5051

5152
The following is an example of an `index.js` file which exports the `handler` function:
5253

5354
```js
54-
var synthetics = require('Synthetics');
55+
const synthetics = require('Synthetics');
5556
const log = require('SyntheticsLogger');
5657

5758
const pageLoadBlueprint = async function () {
59+
// Configure the stage of the API using environment variables
60+
const url = `https://api.example.com/${process.env.stage}/user/books/topbook/`;
5861

59-
// INSERT URL here
60-
const URL = "https://api.example.com/user/books/topbook/";
61-
62-
let page = await synthetics.getPage();
63-
const response = await page.goto(URL, {waitUntil: 'domcontentloaded', timeout: 30000});
64-
//Wait for page to render.
65-
//Increase or decrease wait time based on endpoint being monitored.
62+
const page = await synthetics.getPage();
63+
const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
64+
// Wait for page to render. Increase or decrease wait time based on endpoint being monitored.
6665
await page.waitFor(15000);
67-
// This will take a screenshot that will be included in test output artifacts
66+
// This will take a screenshot that will be included in test output artifacts.
6867
await synthetics.takeScreenshot('loaded', 'loaded');
69-
let pageTitle = await page.title();
68+
const pageTitle = await page.title();
7069
log.info('Page title: ' + pageTitle);
7170
if (response.status() !== 200) {
72-
throw "Failed to load page!";
71+
throw 'Failed to load page!';
7372
}
7473
};
7574

@@ -102,26 +101,28 @@ Using the `Code` class static initializers:
102101

103102
```ts
104103
// To supply the code inline:
105-
const canary = new Canary(this, 'MyCanary', {
106-
test: Test.custom({
104+
new synthetics.Canary(this, 'Inline Canary', {
105+
test: synthetics.Test.custom({
107106
code: synthetics.Code.fromInline('/* Synthetics handler code */'),
108107
handler: 'index.handler', // must be 'index.handler'
109108
}),
110109
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1,
111110
});
112111

113112
// To supply the code from your local filesystem:
114-
const canary = new Canary(this, 'MyCanary', {
115-
test: Test.custom({
113+
new synthetics.Canary(this, 'Asset Canary', {
114+
test: synthetics.Test.custom({
116115
code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')),
117116
handler: 'index.handler', // must end with '.handler'
118117
}),
119118
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1,
120119
});
121120

122121
// To supply the code from a S3 bucket:
123-
const canary = new Canary(this, 'MyCanary', {
124-
test: Test.custom({
122+
import * as s3 from '@aws-cdk/aws-s3';
123+
const bucket = new s3.Bucket(this, 'Code Bucket');
124+
new synthetics.Canary(this, 'Bucket Canary', {
125+
test: synthetics.Test.custom({
125126
code: synthetics.Code.fromBucket(bucket, 'canary.zip'),
126127
handler: 'index.handler', // must end with '.handler'
127128
}),
@@ -150,7 +151,8 @@ You can configure a CloudWatch Alarm on a canary metric. Metrics are emitted by
150151
151152
Create an alarm that tracks the canary metric:
152153
153-
```ts
154+
```ts fixture=canary
155+
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
154156
new cloudwatch.Alarm(this, 'CanaryAlarm', {
155157
metric: canary.metricSuccessPercent(),
156158
evaluationPeriods: 2,

Diff for: packages/@aws-cdk/aws-synthetics/lib/canary.ts

+18
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,14 @@ export interface CanaryProps {
245245
*/
246246
readonly test: Test;
247247

248+
/**
249+
* Key-value pairs that the Synthetics caches and makes available for your canary scripts. Use environment variables
250+
* to apply configuration changes, such as test and production environment configurations, without changing your
251+
* Canary script source code.
252+
*
253+
* @default - No environment variables.
254+
*/
255+
readonly environmentVariables?: { [key: string]: string };
248256
}
249257

250258
/**
@@ -306,6 +314,7 @@ export class Canary extends cdk.Resource {
306314
failureRetentionPeriod: props.failureRetentionPeriod?.toDays(),
307315
successRetentionPeriod: props.successRetentionPeriod?.toDays(),
308316
code: this.createCode(props),
317+
runConfig: this.createRunConfig(props),
309318
});
310319

311320
this.canaryId = resource.attrId;
@@ -410,6 +419,15 @@ export class Canary extends cdk.Resource {
410419
};
411420
}
412421

422+
private createRunConfig(props: CanaryProps): CfnCanary.RunConfigProperty | undefined {
423+
if (!props.environmentVariables) {
424+
return undefined;
425+
}
426+
return {
427+
environmentVariables: props.environmentVariables,
428+
};
429+
}
430+
413431
/**
414432
* Creates a unique name for the canary. The generated name is the physical ID of the canary.
415433
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Fixture with a canary already created, named `canary`
2+
import { Construct, Duration, Stack } from '@aws-cdk/core';
3+
import * as synthetics from '@aws-cdk/aws-synthetics';
4+
import * as path from 'path';
5+
6+
class Fixture extends Stack {
7+
constructor(scope: Construct, id: string) {
8+
super(scope, id);
9+
10+
const canary = new synthetics.Canary(this, 'MyCanary', {
11+
schedule: synthetics.Schedule.rate(Duration.minutes(5)),
12+
test: synthetics.Test.custom({
13+
code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')),
14+
handler: 'index.handler',
15+
}),
16+
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1,
17+
});
18+
19+
/// here
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Fixture with packages imported, but nothing else
2+
import { Construct, Duration, Stack } from '@aws-cdk/core';
3+
import * as synthetics from '@aws-cdk/aws-synthetics';
4+
import * as path from 'path';
5+
6+
class Fixture extends Stack {
7+
constructor(scope: Construct, id: string) {
8+
super(scope, id);
9+
10+
/// here
11+
}
12+
}

Diff for: packages/@aws-cdk/aws-synthetics/test/canary.test.ts

+66-21
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import '@aws-cdk/assert-internal/jest';
2-
import { objectLike } from '@aws-cdk/assert-internal';
2+
import { ABSENT, objectLike } from '@aws-cdk/assert-internal';
33
import * as iam from '@aws-cdk/aws-iam';
44
import * as s3 from '@aws-cdk/aws-s3';
5-
import { App, Duration, Lazy, Stack } from '@aws-cdk/core';
5+
import { Duration, Lazy, Stack } from '@aws-cdk/core';
66
import * as synthetics from '../lib';
77

88
test('Basic canary properties work', () => {
99
// GIVEN
10-
const stack = new Stack(new App(), 'canaries');
10+
const stack = new Stack();
1111

1212
// WHEN
1313
new synthetics.Canary(stack, 'Canary', {
@@ -36,7 +36,7 @@ test('Basic canary properties work', () => {
3636

3737
test('Canary can have generated name', () => {
3838
// GIVEN
39-
const stack = new Stack(new App(), 'canaries');
39+
const stack = new Stack();
4040

4141
// WHEN
4242
new synthetics.Canary(stack, 'Canary', {
@@ -49,13 +49,13 @@ test('Canary can have generated name', () => {
4949

5050
// THEN
5151
expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', {
52-
Name: 'canariescanary8dfb794',
52+
Name: 'canary',
5353
});
5454
});
5555

5656
test('Name validation does not fail when using Tokens', () => {
5757
// GIVEN
58-
const stack = new Stack(new App(), 'canaries');
58+
const stack = new Stack();
5959

6060
// WHEN
6161
new synthetics.Canary(stack, 'Canary', {
@@ -73,7 +73,7 @@ test('Name validation does not fail when using Tokens', () => {
7373

7474
test('Throws when name is specified incorrectly', () => {
7575
// GIVEN
76-
const stack = new Stack(new App(), 'canaries');
76+
const stack = new Stack();
7777

7878
// THEN
7979
expect(() => new synthetics.Canary(stack, 'Canary', {
@@ -89,7 +89,7 @@ test('Throws when name is specified incorrectly', () => {
8989

9090
test('Throws when name has more than 21 characters', () => {
9191
// GIVEN
92-
const stack = new Stack(new App(), 'canaries');
92+
const stack = new Stack();
9393

9494
// THEN
9595
expect(() => new synthetics.Canary(stack, 'Canary', {
@@ -105,7 +105,7 @@ test('Throws when name has more than 21 characters', () => {
105105

106106
test('An existing role can be specified instead of auto-created', () => {
107107
// GIVEN
108-
const stack = new Stack(new App(), 'canaries');
108+
const stack = new Stack();
109109

110110
const role = new iam.Role(stack, 'role', {
111111
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
@@ -131,7 +131,7 @@ test('An existing role can be specified instead of auto-created', () => {
131131

132132
test('An existing bucket and prefix can be specified instead of auto-created', () => {
133133
// GIVEN
134-
const stack = new Stack(new App(), 'canaries');
134+
const stack = new Stack();
135135
const bucket = new s3.Bucket(stack, 'mytestbucket');
136136
const prefix = 'canary';
137137

@@ -153,7 +153,7 @@ test('An existing bucket and prefix can be specified instead of auto-created', (
153153

154154
test('Runtime can be specified', () => {
155155
// GIVEN
156-
const stack = new Stack(new App(), 'canaries');
156+
const stack = new Stack();
157157

158158
// WHEN
159159
new synthetics.Canary(stack, 'Canary', {
@@ -170,9 +170,54 @@ test('Runtime can be specified', () => {
170170
});
171171
});
172172

173+
test('environment variables can be specified', () => {
174+
// GIVEN
175+
const stack = new Stack();
176+
const environmentVariables = {
177+
TEST_KEY_1: 'TEST_VALUE_1',
178+
TEST_KEY_2: 'TEST_VALUE_2',
179+
};
180+
181+
// WHEN
182+
new synthetics.Canary(stack, 'Canary', {
183+
runtime: synthetics.Runtime.SYNTHETICS_1_0,
184+
test: synthetics.Test.custom({
185+
handler: 'index.handler',
186+
code: synthetics.Code.fromInline('/* Synthetics handler code */'),
187+
}),
188+
environmentVariables: environmentVariables,
189+
});
190+
191+
// THEN
192+
expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', {
193+
RunConfig: {
194+
EnvironmentVariables: environmentVariables,
195+
},
196+
});
197+
});
198+
199+
test('environment variables are skipped if not provided', () => {
200+
// GIVEN
201+
const stack = new Stack();
202+
203+
// WHEN
204+
new synthetics.Canary(stack, 'Canary', {
205+
runtime: synthetics.Runtime.SYNTHETICS_1_0,
206+
test: synthetics.Test.custom({
207+
handler: 'index.handler',
208+
code: synthetics.Code.fromInline('/* Synthetics handler code */'),
209+
}),
210+
});
211+
212+
// THEN
213+
expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', {
214+
RunConfig: ABSENT,
215+
});
216+
});
217+
173218
test('Runtime can be customized', () => {
174219
// GIVEN
175-
const stack = new Stack(new App(), 'canaries');
220+
const stack = new Stack();
176221

177222
// WHEN
178223
new synthetics.Canary(stack, 'Canary', {
@@ -191,7 +236,7 @@ test('Runtime can be customized', () => {
191236

192237
test('Schedule can be set with Rate', () => {
193238
// GIVEN
194-
const stack = new Stack(new App(), 'canaries');
239+
const stack = new Stack();
195240

196241
// WHEN
197242
new synthetics.Canary(stack, 'Canary', {
@@ -211,7 +256,7 @@ test('Schedule can be set with Rate', () => {
211256

212257
test('Schedule can be set to 1 minute', () => {
213258
// GIVEN
214-
const stack = new Stack(new App(), 'canaries');
259+
const stack = new Stack();
215260

216261
// WHEN
217262
new synthetics.Canary(stack, 'Canary', {
@@ -231,7 +276,7 @@ test('Schedule can be set to 1 minute', () => {
231276

232277
test('Schedule can be set with Expression', () => {
233278
// GIVEN
234-
const stack = new Stack(new App(), 'canaries');
279+
const stack = new Stack();
235280

236281
// WHEN
237282
new synthetics.Canary(stack, 'Canary', {
@@ -251,7 +296,7 @@ test('Schedule can be set with Expression', () => {
251296

252297
test('Schedule can be set to run once', () => {
253298
// GIVEN
254-
const stack = new Stack(new App(), 'canaries');
299+
const stack = new Stack();
255300

256301
// WHEN
257302
new synthetics.Canary(stack, 'Canary', {
@@ -271,7 +316,7 @@ test('Schedule can be set to run once', () => {
271316

272317
test('Throws when rate above 60 minutes', () => {
273318
// GIVEN
274-
const stack = new Stack(new App(), 'canaries');
319+
const stack = new Stack();
275320

276321
// THEN
277322
expect(() => new synthetics.Canary(stack, 'Canary', {
@@ -287,7 +332,7 @@ test('Throws when rate above 60 minutes', () => {
287332

288333
test('Throws when rate above is not a whole number of minutes', () => {
289334
// GIVEN
290-
const stack = new Stack(new App(), 'canaries');
335+
const stack = new Stack();
291336

292337
// THEN
293338
expect(() => new synthetics.Canary(stack, 'Canary', {
@@ -303,7 +348,7 @@ test('Throws when rate above is not a whole number of minutes', () => {
303348

304349
test('Can share artifacts bucket between canaries', () => {
305350
// GIVEN
306-
const stack = new Stack(new App(), 'canaries');
351+
const stack = new Stack();
307352

308353
// WHEN
309354
const canary1 = new synthetics.Canary(stack, 'Canary1', {
@@ -331,7 +376,7 @@ test('Can share artifacts bucket between canaries', () => {
331376

332377
test('can specify custom test', () => {
333378
// GIVEN
334-
const stack = new Stack(new App(), 'canaries');
379+
const stack = new Stack();
335380

336381
// WHEN
337382
new synthetics.Canary(stack, 'Canary', {
@@ -355,4 +400,4 @@ test('can specify custom test', () => {
355400
};`,
356401
},
357402
});
358-
});
403+
});

0 commit comments

Comments
 (0)