Skip to content

Commit

Permalink
fix(apigateway): deployment not invalidated when integration is changed
Browse files Browse the repository at this point in the history
The calculation of the deployment logical id is based on a hash of the apigateway model, which includes components such as the resource and method configurations. method integrations, which are part of the method configuration are also included, and could possible include references to other resources such as lambda functions.

When the hash was calculated, tokens have been resolved using `StringConcat` which basically converted all tokens to `[Object object]`. This means, for example, that if we changed the reference of an integration lambda to a different handler, the hash would remain the same.

There is no apparent reason why we can't simply resolve tokens using the CloudFormation concatenation function during "prepare", which means that if we now reference a different resource, the token will resolve to a different value and deployment will be invalidated.

Fixes #4551
Fixes aws-samples/aws-cdk-intro-workshop#83
  • Loading branch information
Elad Ben-Israel committed Oct 17, 2019
1 parent f7ea9dd commit a641676
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 18 deletions.
13 changes: 3 additions & 10 deletions packages/@aws-cdk/aws-apigateway/lib/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,13 @@ class LatestDeploymentResource extends CfnDeployment {
* add via `addToLogicalId`.
*/
protected prepare() {
const stack = Stack.of(this);

// if hash components were added to the deployment, we use them to calculate
// a logical ID for the deployment resource.
if (this.hashComponents.length > 0) {
const md5 = crypto.createHash('md5');
this.hashComponents
.map(c => {
return Tokenization.resolve(c, {
scope: this,
resolver: new DefaultTokenResolver(new StringConcat()),
preparing: true,
});
})
.forEach(c => md5.update(JSON.stringify(c)));

this.hashComponents.map(x => stack.resolve(x)).forEach(c => md5.update(JSON.stringify(c)));
this.overrideLogicalId(this.originalLogicalId + md5.digest("hex"));
}
super.prepare();
Expand Down
53 changes: 45 additions & 8 deletions packages/@aws-cdk/aws-apigateway/test/test.deployment.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { expect, haveResource, ResourcePart, SynthUtils } from '@aws-cdk/assert';
import cdk = require('@aws-cdk/core');
import lambda = require('@aws-cdk/aws-lambda');
import { CfnResource, Lazy, Stack } from '@aws-cdk/core';
import { Test } from 'nodeunit';
import path = require('path');
import apigateway = require('../lib');

export = {
'minimal setup'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const stack = new Stack();
const api = new apigateway.RestApi(stack, 'api', { deploy: false, cloudWatchRole: false });
api.root.addMethod('GET');

Expand Down Expand Up @@ -57,7 +59,7 @@ export = {

'"retainDeployments" can be used to control the deletion policy of the resource'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const stack = new Stack();
const api = new apigateway.RestApi(stack, 'api', { deploy: false, cloudWatchRole: false });
api.root.addMethod('GET');

Expand Down Expand Up @@ -110,7 +112,7 @@ export = {

'"description" can be set on the deployment'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const stack = new Stack();
const api = new apigateway.RestApi(stack, 'api', { deploy: false, cloudWatchRole: false });
api.root.addMethod('GET');

Expand All @@ -127,7 +129,7 @@ export = {

'"addToLogicalId" will "salt" the logical ID of the deployment resource'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const stack = new Stack();
const api = new apigateway.RestApi(stack, 'api', { deploy: false, cloudWatchRole: false });
const deployment = new apigateway.Deployment(stack, 'deployment', { api });
api.root.addMethod('GET');
Expand All @@ -145,7 +147,7 @@ export = {

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

const template2 = synthesize();
test.ok(template2.Resources.deployment33381975a12dfe81474913364dc31c06e37f9449);
Expand All @@ -159,12 +161,12 @@ export = {

'"addDependency" can be used to add a resource as a dependency'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const stack = new Stack();
const api = new apigateway.RestApi(stack, 'api', { deploy: false, cloudWatchRole: false });
const deployment = new apigateway.Deployment(stack, 'deployment', { api });
api.root.addMethod('GET');

const dep = new cdk.CfnResource(stack, 'MyResource', { type: 'foo' });
const dep = new CfnResource(stack, 'MyResource', { type: 'foo' });

// WHEN
deployment.node.addDependency(dep);
Expand All @@ -175,4 +177,39 @@ export = {

test.done();
},

'integration change invalidates deployment'(test: Test) {
// GIVEN
const stack1 = new Stack();
const stack2 = new Stack();
const handler1 = new lambda.Function(stack1, 'handler1', {
code: lambda.Code.fromAsset(path.join(__dirname, 'integ.cors.handler')),
runtime: lambda.Runtime.NODEJS_10_X,
handler: 'index.handler'
});
const handler2 = new lambda.Function(stack2, 'handler2', {
code: lambda.Code.fromAsset(path.join(__dirname, 'integ.cors.handler')),
runtime: lambda.Runtime.NODEJS_10_X,
handler: 'index.handler'
});

// WHEN
const api1 = new apigateway.RestApi(stack1, 'myapi', {
defaultIntegration: new apigateway.LambdaIntegration(handler1)
});
const api2 = new apigateway.RestApi(stack2, 'myapi', {
defaultIntegration: new apigateway.LambdaIntegration(handler2)
});
api1.root.addMethod('GET');
api2.root.addMethod('GET');

// THEN
expect(stack1).to(haveResource('AWS::ApiGateway::Stage', {
DeploymentId: { Ref: 'myapiDeploymentB7EF8EB7e0b8372768854261d2d1218739e0a307' }
}));
expect(stack2).to(haveResource('AWS::ApiGateway::Stage', {
DeploymentId: { Ref: 'myapiDeploymentB7EF8EB77c517352b0f7ab73c333e36585c8f1f3' }
}));
test.done();
}
};

0 comments on commit a641676

Please sign in to comment.