Skip to content

Commit 8a5d7f4

Browse files
Elad Ben-IsraelCurtis Eppel
authored andcommitted
chore: do not use "synthesize" and "prepare" in the cdk (aws#9410)
In 2.x we plan to deprecate support for the `synthesize()` and `prepare()` hooks in `Construct`. See [RFC] for motivation. This change does not remove support for these hooks, but it does remove any usage of these hooks from the AWS Construct Library. - aws-apigateway: the calculated logical ID of Deployment resources is now done through a Lazy instead of in `prepare()`. - aws-lambda: the calculated logical ID of Version resources is now done through a Lazy instead of in `prepare()`. - core: `Stack.synthesize()` is now called `_synthesizeTemplate()` and is explicitly called from `app.synth()`. - core: `TreeEtadata.synthesize()` is now called `_synthesizeTree()` and is explicitly called from `app.synth()`. The logical IDs of Lambda Version and API Gateway Deployment resources will be different after this change due to a latent bug in the previous implementation. The `prepare()` methods are called _before_ resource dependencies are resolved, which means that the hash in the logical ID did not include dependencies. Now it includes dependencies and therefore these IDs have changed. Since both of these resources are stateless, this does not introduce risk to production systems. See more details [here]. Furthermore: all calls to `ConstructNode.prepare()` were converted to `app.synth()`. Related: aws/aws-cdk-rfcs#192 [RFC]: https://github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md [here]: aws#9410 (comment) BREAKING CHANGE: `lambda.Version` and `apigateway.Deployment` resources with auto-generated IDs will be replaced as we fixed a bug which ignored resource dependencies when generating these logical IDs. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 5c0b69b commit 8a5d7f4

File tree

14 files changed

+124
-90
lines changed

14 files changed

+124
-90
lines changed

packages/@aws-cdk/aws-apigateway/lib/deployment.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ interface LatestDeploymentResourceProps {
123123

124124
class LatestDeploymentResource extends CfnDeployment {
125125
private hashComponents = new Array<any>();
126-
private originalLogicalId: string;
126+
127127
private api: IRestApi;
128128

129129
constructor(scope: Construct, id: string, props: LatestDeploymentResourceProps) {
@@ -133,7 +133,31 @@ class LatestDeploymentResource extends CfnDeployment {
133133
});
134134

135135
this.api = props.restApi;
136-
this.originalLogicalId = Stack.of(this).getLogicalId(this);
136+
137+
const originalLogicalId = Stack.of(this).getLogicalId(this);
138+
139+
this.overrideLogicalId(Lazy.stringValue({ produce: ctx => {
140+
const hash = [ ...this.hashComponents ];
141+
142+
if (this.api instanceof RestApi || this.api instanceof SpecRestApi) { // Ignore IRestApi that are imported
143+
144+
// Add CfnRestApi to the logical id so a new deployment is triggered when any of its properties change.
145+
const cfnRestApiCF = (this.api.node.defaultChild as any)._toCloudFormation();
146+
hash.push(ctx.resolve(cfnRestApiCF));
147+
}
148+
149+
let lid = originalLogicalId;
150+
151+
// if hash components were added to the deployment, we use them to calculate
152+
// a logical ID for the deployment resource.
153+
if (hash.length > 0) {
154+
const md5 = crypto.createHash('md5');
155+
hash.map(x => ctx.resolve(x)).forEach(c => md5.update(JSON.stringify(c)));
156+
lid += md5.digest('hex');
157+
}
158+
159+
return lid;
160+
}}));
137161
}
138162

139163
/**
@@ -149,28 +173,4 @@ class LatestDeploymentResource extends CfnDeployment {
149173

150174
this.hashComponents.push(data);
151175
}
152-
153-
/**
154-
* Hooks into synthesis to calculate a logical ID that hashes all the components
155-
* add via `addToLogicalId`.
156-
*/
157-
protected prepare() {
158-
if (this.api instanceof RestApi || this.api instanceof SpecRestApi) { // Ignore IRestApi that are imported
159-
160-
// Add CfnRestApi to the logical id so a new deployment is triggered when any of its properties change.
161-
const cfnRestApiCF = (this.api.node.defaultChild as any)._toCloudFormation();
162-
this.addToLogicalId(Stack.of(this).resolve(cfnRestApiCF));
163-
}
164-
165-
const stack = Stack.of(this);
166-
167-
// if hash components were added to the deployment, we use them to calculate
168-
// a logical ID for the deployment resource.
169-
if (this.hashComponents.length > 0) {
170-
const md5 = crypto.createHash('md5');
171-
this.hashComponents.map(x => stack.resolve(x)).forEach(c => md5.update(JSON.stringify(c)));
172-
this.overrideLogicalId(this.originalLogicalId + md5.digest('hex'));
173-
}
174-
super.prepare();
175-
}
176176
}

packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120
"BooksApi60AC975F"
121121
]
122122
},
123-
"BooksApiDeployment86CA39AFc1570c78b1ea90526c0309cd74b7b8d0": {
123+
"BooksApiDeployment86CA39AF4ff82f86c127f53c9de94d266b1906be": {
124124
"Type": "AWS::ApiGateway::Deployment",
125125
"Properties": {
126126
"RestApiId": {
@@ -141,7 +141,7 @@
141141
"Ref": "BooksApi60AC975F"
142142
},
143143
"DeploymentId": {
144-
"Ref": "BooksApiDeployment86CA39AFc1570c78b1ea90526c0309cd74b7b8d0"
144+
"Ref": "BooksApiDeployment86CA39AF4ff82f86c127f53c9de94d266b1906be"
145145
},
146146
"StageName": "prod"
147147
}

packages/@aws-cdk/aws-apigateway/test/test.deployment.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,16 @@ export = {
150150
// the logical ID changed
151151
const template = synthesize();
152152
test.ok(!template.Resources.deployment33381975bba46c5132329b81e7befcbbba5a0e75, 'old resource id is not deleted');
153-
test.ok(template.Resources.deployment33381975075f46a4503208d69fcffed2f263c48c,
154-
`new resource deployment33381975075f46a4503208d69fcffed2f263c48c is not created, instead found ${Object.keys(template.Resources)}`);
153+
test.ok(template.Resources.deployment333819758aa4cdb9d204502b959c4903f4d5d29f,
154+
`new resource deployment333819758aa4cdb9d204502b959c4903f4d5d29f is not created, instead found ${Object.keys(template.Resources)}`);
155155

156156
// tokens supported, and are resolved upon synthesis
157157
const value = 'hello hello';
158158
deployment.addToLogicalId({ foo: Lazy.stringValue({ produce: () => value }) });
159159

160160
const template2 = synthesize();
161-
test.ok(template2.Resources.deployment33381975b6d7672e4c9afd0b741e41d07739786b,
162-
`resource deployment33381975b6d7672e4c9afd0b741e41d07739786b not found, instead found ${Object.keys(template2.Resources)}`);
161+
test.ok(template2.Resources.deployment333819758d91bed959c6bd6268ba84f6d33e888e,
162+
`resource deployment333819758d91bed959c6bd6268ba84f6d33e888e not found, instead found ${Object.keys(template2.Resources)}`);
163163

164164
test.done();
165165

packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ describe('with Lambda@Edge functions', () => {
307307
{
308308
EventType: 'origin-request',
309309
LambdaFunctionARN: {
310-
Ref: 'FunctionCurrentVersion4E2B22619c0305f954e58f25575548280c0a3629',
310+
Ref: 'FunctionCurrentVersion4E2B2261477a5ae8059bbaa7813f752292c0f65e',
311311
},
312312
},
313313
],
@@ -341,7 +341,7 @@ describe('with Lambda@Edge functions', () => {
341341
{
342342
EventType: 'viewer-request',
343343
LambdaFunctionARN: {
344-
Ref: 'FunctionCurrentVersion4E2B22619c0305f954e58f25575548280c0a3629',
344+
Ref: 'FunctionCurrentVersion4E2B2261477a5ae8059bbaa7813f752292c0f65e',
345345
},
346346
},
347347
],

packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export = nodeunit.testCase({
2626
actions: [action],
2727
});
2828

29-
cdk.ConstructNode.prepare(stack.node);
29+
app.synth();
3030

3131
_assertPermissionGranted(test, stack, pipelineRole.statements, 'iam:PassRole', action.deploymentRole.roleArn);
3232

packages/@aws-cdk/aws-ec2/test/connections.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, haveResource } from '@aws-cdk/assert';
2-
import { App, ConstructNode, Stack } from '@aws-cdk/core';
2+
import { App, Stack } from '@aws-cdk/core';
33
import { nodeunitShim, Test } from 'nodeunit-shim';
44

55
import {
@@ -185,7 +185,7 @@ nodeunitShim({
185185
sg2.connections.allowFrom(sg1, Port.tcp(100));
186186

187187
// THEN -- both rules are in Stack2
188-
ConstructNode.prepare(app.node);
188+
app.synth();
189189

190190
expect(stack2).to(haveResource('AWS::EC2::SecurityGroupIngress', {
191191
GroupId: { 'Fn::GetAtt': [ 'SecurityGroupDD263621', 'GroupId' ] },
@@ -216,7 +216,7 @@ nodeunitShim({
216216
sg2.connections.allowTo(sg1, Port.tcp(100));
217217

218218
// THEN -- both rules are in Stack2
219-
ConstructNode.prepare(app.node);
219+
app.synth();
220220

221221
expect(stack2).to(haveResource('AWS::EC2::SecurityGroupIngress', {
222222
GroupId: { 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttSecurityGroupDD263621GroupIdDF6F8B09' },
@@ -249,7 +249,7 @@ nodeunitShim({
249249
sg2.connections.allowFrom(sg1b, Port.tcp(100));
250250

251251
// THEN -- both egress rules are in Stack2
252-
ConstructNode.prepare(app.node);
252+
app.synth();
253253

254254
expect(stack2).to(haveResource('AWS::EC2::SecurityGroupEgress', {
255255
GroupId: { 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttSecurityGroupAED40ADC5GroupId1D10C76A' },

packages/@aws-cdk/aws-lambda/lib/function.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,18 @@ export class Function extends FunctionBase {
332332
...this.currentVersionOptions,
333333
});
334334

335+
// override the version's logical ID with a lazy string which includes the
336+
// hash of the function itself, so a new version resource is created when
337+
// the function configuration changes.
338+
const cfn = this._currentVersion.node.defaultChild as CfnResource;
339+
const originalLogicalId = this.stack.resolve(cfn.logicalId) as string;
340+
341+
cfn.overrideLogicalId(Lazy.stringValue({ produce: _ => {
342+
const hash = calculateFunctionHash(this);
343+
const logicalId = trimFromStart(originalLogicalId, 255 - 32);
344+
return `${logicalId}${hash}`;
345+
}}));
346+
335347
return this._currentVersion;
336348
}
337349

@@ -739,23 +751,6 @@ export class Function extends FunctionBase {
739751
return this._logGroup;
740752
}
741753

742-
protected prepare() {
743-
super.prepare();
744-
745-
// if we have a current version resource, override it's logical id
746-
// so that it includes the hash of the function code and it's configuration.
747-
if (this._currentVersion) {
748-
const stack = Stack.of(this);
749-
const cfn = this._currentVersion.node.defaultChild as CfnResource;
750-
const originalLogicalId: string = stack.resolve(cfn.logicalId);
751-
752-
const hash = calculateFunctionHash(this);
753-
754-
const logicalId = trimFromStart(originalLogicalId, 255 - 32);
755-
cfn.overrideLogicalId(`${logicalId}${hash}`);
756-
}
757-
}
758-
759754
private renderEnvironment() {
760755
if (!this.environment || Object.keys(this.environment).length === 0) {
761756
return undefined;

packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
"MyLambdaServiceRole4539ECB6"
8686
]
8787
},
88-
"MyLambdaCurrentVersionE7A382CC86b18af374d6e380aa07074d2490e2df": {
88+
"MyLambdaCurrentVersionE7A382CC721de083c6b4b6360a9c534b79eb610e": {
8989
"Type": "AWS::Lambda::Version",
9090
"Properties": {
9191
"FunctionName": {
@@ -103,7 +103,7 @@
103103
},
104104
"Qualifier": {
105105
"Fn::GetAtt": [
106-
"MyLambdaCurrentVersionE7A382CC86b18af374d6e380aa07074d2490e2df",
106+
"MyLambdaCurrentVersionE7A382CC721de083c6b4b6360a9c534b79eb610e",
107107
"Version"
108108
]
109109
},
@@ -118,7 +118,7 @@
118118
},
119119
"FunctionVersion": {
120120
"Fn::GetAtt": [
121-
"MyLambdaCurrentVersionE7A382CC86b18af374d6e380aa07074d2490e2df",
121+
"MyLambdaCurrentVersionE7A382CC721de083c6b4b6360a9c534b79eb610e",
122122
"Version"
123123
]
124124
},

packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export = {
134134
},
135135
'FunctionVersion': {
136136
'Fn::GetAtt': [
137-
'FnCurrentVersion17A89ABB19ed45993ff69fd011ae9fd4ab6e2005',
137+
'FnCurrentVersion17A89ABBab5c765f3c55e4e61583b51b00a95742',
138138
'Version',
139139
],
140140
},

packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,6 @@ test('a notification destination can specify a set of dependencies that must be
290290

291291
bucket.addObjectCreatedNotification(dest);
292292

293-
cdk.ConstructNode.prepare(stack.node);
294-
295293
expect(SynthUtils.synthesize(stack).template.Resources.BucketNotifications8F2E257D).toEqual({
296294
Type: 'Custom::S3BucketNotifications',
297295
Properties: {

0 commit comments

Comments
 (0)