diff --git a/packages/@aws-cdk/aws-apigateway/lib/integration.ts b/packages/@aws-cdk/aws-apigateway/lib/integration.ts index d7a9ec74f3b34..6f305e2932efb 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integration.ts @@ -134,6 +134,20 @@ export interface IntegrationProps { readonly options?: IntegrationOptions; } +/** + * Result of binding an Integration to the Method + */ +export interface IntegrationConfig { + /** + * This value is included in computing the Deployment's fingerprint. When the fingerprint + * changes, a new deployment is triggered. + * This property should contain values associated with the Integration that upon changing + * should trigger a fresh the Deployment needs to be refreshed. + * @default undefined deployments are not triggered for any change to this integration. + */ + readonly deploymentToken?: string; +} + /** * Base class for backend integrations for an API Gateway method. * @@ -156,7 +170,7 @@ export class Integration { * Can be overridden by subclasses to allow the integration to interact with the method * being integrated, access the REST API object, method ARNs, etc. */ - public bind(_method: Method) { + public bind(_method: Method): IntegrationConfig | undefined { return; } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts index cd3854e2abd22..6300c3b77379d 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts @@ -1,5 +1,5 @@ import * as cdk from '@aws-cdk/core'; -import { Integration, IntegrationOptions, IntegrationType } from '../integration'; +import { Integration, IntegrationConfig, IntegrationOptions, IntegrationType } from '../integration'; import { Method } from '../method'; import { parseAwsApiCall } from '../util'; @@ -92,7 +92,8 @@ export class AwsIntegration extends Integration { }); } - public bind(method: Method) { + public bind(method: Method): IntegrationConfig | undefined { this.scope = method; + return; } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index fe5e81678e85e..715c1d91237fe 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Lazy } from '@aws-cdk/core'; -import { IntegrationOptions } from '../integration'; +import { Lazy, Token } from '@aws-cdk/core'; +import { IntegrationConfig, IntegrationOptions } from '../integration'; import { Method } from '../method'; import { AwsIntegration } from './aws'; @@ -52,7 +52,7 @@ export class LambdaIntegration extends AwsIntegration { this.enableTest = options.allowTestInvoke === undefined ? true : false; } - public bind(method: Method) { + public bind(method: Method): IntegrationConfig | undefined { super.bind(method); const principal = new iam.ServicePrincipal('apigateway.amazonaws.com'); @@ -72,5 +72,14 @@ export class LambdaIntegration extends AwsIntegration { sourceArn: method.testMethodArn, }); } + + const cfnFunction = this.handler.node.defaultChild as lambda.CfnFunction; + let deploymentToken; + if (!Token.isUnresolved(cfnFunction.functionName)) { + deploymentToken = JSON.stringify({ functionName: cfnFunction.functionName }); + } + return { + deploymentToken, + }; } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 8721d9e9da114..b9a9143df6506 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -192,6 +192,9 @@ export class Method extends Resource { authorizer._attachToApi(this.api); } + const integration = props.integration ?? this.resource.defaultIntegration ?? new MockIntegration(); + const bindResult = integration.bind(this); + const methodProps: CfnMethodProps = { resourceId: props.resource.resourceId, restApiId: this.api.restApiId, @@ -201,7 +204,7 @@ export class Method extends Resource { authorizationType, authorizerId, requestParameters: options.requestParameters || defaultMethodOptions.requestParameters, - integration: this.renderIntegration(props.integration), + integration: this.renderIntegration(integration), methodResponses: this.renderMethodResponses(options.methodResponses), requestModels: this.renderRequestModels(options.requestModels), requestValidatorId: this.requestValidatorId(options), @@ -219,7 +222,12 @@ export class Method extends Resource { const deployment = props.resource.api.latestDeployment; if (deployment) { deployment.node.addDependency(resource); - deployment.addToLogicalId({ method: methodProps }); + deployment.addToLogicalId({ + method: { + ...methodProps, + integrationToken: bindResult?.deploymentToken, + }, + }); } } @@ -255,19 +263,7 @@ export class Method extends Resource { return this.api.arnForExecuteApi(this.httpMethod, pathForArn(this.resource.path), 'test-invoke-stage'); } - private renderIntegration(integration?: Integration): CfnMethod.IntegrationProperty { - if (!integration) { - // use defaultIntegration from API if defined - if (this.resource.defaultIntegration) { - return this.renderIntegration(this.resource.defaultIntegration); - } - - // fallback to mock - return this.renderIntegration(new MockIntegration()); - } - - integration.bind(this); - + private renderIntegration(integration: Integration): CfnMethod.IntegrationProperty { const options = integration._props.options || { }; let credentials; diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json index 3404f37880155..92ca8ad632bfc 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json @@ -120,7 +120,7 @@ "BooksApi60AC975F" ] }, - "BooksApiDeployment86CA39AF7e6c771d47a1a3777eba99bffc037822": { + "BooksApiDeployment86CA39AFc1570c78b1ea90526c0309cd74b7b8d0": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { @@ -141,7 +141,7 @@ "Ref": "BooksApi60AC975F" }, "DeploymentId": { - "Ref": "BooksApiDeployment86CA39AF7e6c771d47a1a3777eba99bffc037822" + "Ref": "BooksApiDeployment86CA39AFc1570c78b1ea90526c0309cd74b7b8d0" }, "StageName": "prod" } diff --git a/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts b/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts index 4a979e7671f85..afc1a2230e848 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts @@ -215,4 +215,48 @@ export = { test.done(); }, + + 'fingerprint is computed when functionName is specified'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const restapi = new apigateway.RestApi(stack, 'RestApi'); + const method = restapi.root.addMethod('ANY'); + const handler = new lambda.Function(stack, 'MyFunc', { + functionName: 'ThisFunction', + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('loo'), + }); + const integration = new apigateway.LambdaIntegration(handler); + + // WHEN + const bindResult = integration.bind(method); + + // THEN + test.ok(bindResult?.deploymentToken); + test.deepEqual(bindResult!.deploymentToken, '{"functionName":"ThisFunction"}'); + + test.done(); + }, + + 'fingerprint is not computed when functionName is not specified'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const restapi = new apigateway.RestApi(stack, 'RestApi'); + const method = restapi.root.addMethod('ANY'); + const handler = new lambda.Function(stack, 'MyFunc', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('loo'), + }); + const integration = new apigateway.LambdaIntegration(handler); + + // WHEN + const bindResult = integration.bind(method); + + // THEN + test.equals(bindResult?.deploymentToken, undefined); + + test.done(); + }, };