Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(apigateway): modernize Integration.bind() #8814

Merged
merged 3 commits into from Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 45 additions & 12 deletions packages/@aws-cdk/aws-apigateway/lib/integration.ts
Expand Up @@ -135,9 +135,33 @@ export interface IntegrationProps {
}

/**
* Result of binding an Integration to the Method
* Result of binding an Integration to a Method.
*/
export interface IntegrationConfig {
/**
* Integration options.
* @default - no integration options
*/
readonly options?: IntegrationOptions;

/**
* Specifies an API method integration type.
*/
readonly type: IntegrationType;

/**
* The Uniform Resource Identifier (URI) for the integration.
* @see https://docs.aws.amazon.com/apigateway/api-reference/resource/integration/#uri
* @default - no URI. Usually applies to MOCK integration
*/
readonly uri?: string;

/**
* The integration's HTTP method type.
* @default - no integration method specified.
*/
readonly integrationHttpMethod?: string;

/**
* This value is included in computing the Deployment's fingerprint. When the fingerprint
* changes, a new deployment is triggered.
Expand All @@ -155,23 +179,32 @@ export interface IntegrationConfig {
* or implement on your own by specifying the set of props.
*/
export class Integration {
constructor(private readonly props: IntegrationProps) { }

/**
* Allows `Method` to access the integration props.
*
* @internal
*/
public get _props() {
return this.props;
constructor(private readonly props: IntegrationProps) {
const options = this.props.options || { };
if (options.credentialsPassthrough !== undefined && options.credentialsRole !== undefined) {
throw new Error('\'credentialsPassthrough\' and \'credentialsRole\' are mutually exclusive');
}

if (options.connectionType === ConnectionType.VPC_LINK && options.vpcLink === undefined) {
throw new Error('\'connectionType\' of VPC_LINK requires \'vpcLink\' prop to be set');
}

if (options.connectionType === ConnectionType.INTERNET && options.vpcLink !== undefined) {
throw new Error('cannot set \'vpcLink\' where \'connectionType\' is INTERNET');
}
}

/**
* 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): IntegrationConfig | undefined {
return;
public bind(_method: Method): IntegrationConfig {
return {
options: this.props.options,
type: this.props.type,
uri: this.props.uri,
integrationHttpMethod: this.props.integrationHttpMethod,
};
}
}

Expand Down
5 changes: 3 additions & 2 deletions packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts
Expand Up @@ -92,8 +92,9 @@ export class AwsIntegration extends Integration {
});
}

public bind(method: Method): IntegrationConfig | undefined {
public bind(method: Method): IntegrationConfig {
const bindResult = super.bind(method);
this.scope = method;
return;
return bindResult;
}
}
5 changes: 3 additions & 2 deletions packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts
Expand Up @@ -52,8 +52,8 @@ export class LambdaIntegration extends AwsIntegration {
this.enableTest = options.allowTestInvoke === undefined ? true : false;
}

public bind(method: Method): IntegrationConfig | undefined {
super.bind(method);
public bind(method: Method): IntegrationConfig {
const bindResult = super.bind(method);
const principal = new iam.ServicePrincipal('apigateway.amazonaws.com');

const desc = `${method.api.node.uniqueId}.${method.httpMethod}.${method.resource.path.replace(/\//g, '.')}`;
Expand All @@ -79,6 +79,7 @@ export class LambdaIntegration extends AwsIntegration {
deploymentToken = JSON.stringify({ functionName: cfnFunction.functionName });
}
return {
...bindResult,
deploymentToken,
};
}
Expand Down
27 changes: 7 additions & 20 deletions packages/@aws-cdk/aws-apigateway/lib/method.ts
@@ -1,7 +1,7 @@
import { Construct, Resource, Stack } from '@aws-cdk/core';
import { CfnMethod, CfnMethodProps } from './apigateway.generated';
import { Authorizer, IAuthorizer } from './authorizer';
import { ConnectionType, Integration } from './integration';
import { Integration, IntegrationConfig } from './integration';
import { MockIntegration } from './integrations/mock';
import { MethodResponse } from './methodresponse';
import { IModel } from './model';
Expand Down Expand Up @@ -204,7 +204,7 @@ export class Method extends Resource {
authorizationType,
authorizerId,
requestParameters: options.requestParameters || defaultMethodOptions.requestParameters,
integration: this.renderIntegration(integration),
integration: this.renderIntegration(bindResult),
methodResponses: this.renderMethodResponses(options.methodResponses),
requestModels: this.renderRequestModels(options.requestModels),
requestValidatorId: this.requestValidatorId(options),
Expand Down Expand Up @@ -263,22 +263,9 @@ export class Method extends Resource {
return this.api.arnForExecuteApi(this.httpMethod, pathForArn(this.resource.path), 'test-invoke-stage');
}

private renderIntegration(integration: Integration): CfnMethod.IntegrationProperty {
const options = integration._props.options || { };

private renderIntegration(bindResult: IntegrationConfig): CfnMethod.IntegrationProperty {
const options = bindResult.options ?? {};
let credentials;
if (options.credentialsPassthrough !== undefined && options.credentialsRole !== undefined) {
throw new Error('\'credentialsPassthrough\' and \'credentialsRole\' are mutually exclusive');
}

if (options.connectionType === ConnectionType.VPC_LINK && options.vpcLink === undefined) {
throw new Error('\'connectionType\' of VPC_LINK requires \'vpcLink\' prop to be set');
}

if (options.connectionType === ConnectionType.INTERNET && options.vpcLink !== undefined) {
throw new Error('cannot set \'vpcLink\' where \'connectionType\' is INTERNET');
}

if (options.credentialsRole) {
credentials = options.credentialsRole.roleArn;
} else if (options.credentialsPassthrough) {
Expand All @@ -288,12 +275,12 @@ export class Method extends Resource {
}

return {
type: integration._props.type,
uri: integration._props.uri,
type: bindResult.type,
uri: bindResult.uri,
cacheKeyParameters: options.cacheKeyParameters,
cacheNamespace: options.cacheNamespace,
contentHandling: options.contentHandling,
integrationHttpMethod: integration._props.integrationHttpMethod,
integrationHttpMethod: bindResult.integrationHttpMethod,
requestParameters: options.requestParameters,
requestTemplates: options.requestTemplates,
passthroughBehavior: options.passthroughBehavior,
Expand Down
58 changes: 58 additions & 0 deletions packages/@aws-cdk/aws-apigateway/test/test.integration.ts
@@ -0,0 +1,58 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';
import { Test } from 'nodeunit';
import * as apigw from '../lib';

export = {
'integration "credentialsRole" and "credentialsPassthrough" are mutually exclusive'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') });

// THEN
test.throws(() => new apigw.Integration({
type: apigw.IntegrationType.AWS_PROXY,
options: {
credentialsPassthrough: true,
credentialsRole: role,
},
}), /'credentialsPassthrough' and 'credentialsRole' are mutually exclusive/);
test.done();
},

'integration connectionType VpcLink requires vpcLink to be set'(test: Test) {
test.throws(() => new apigw.Integration({
type: apigw.IntegrationType.HTTP_PROXY,
integrationHttpMethod: 'ANY',
options: {
connectionType: apigw.ConnectionType.VPC_LINK,
},
}), /'connectionType' of VPC_LINK requires 'vpcLink' prop to be set/);
test.done();
},

'connectionType of INTERNET and vpcLink are mutually exclusive'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', {
vpc,
});
const link = new apigw.VpcLink(stack, 'link', {
targets: [nlb],
});

// THEN
test.throws(() => new apigw.Integration({
type: apigw.IntegrationType.HTTP_PROXY,
integrationHttpMethod: 'ANY',
options: {
connectionType: apigw.ConnectionType.INTERNET,
vpcLink: link,
},
}), /cannot set 'vpcLink' where 'connectionType' is INTERNET/);
test.done();
},
};
68 changes: 0 additions & 68 deletions packages/@aws-cdk/aws-apigateway/test/test.method.ts
@@ -1,6 +1,4 @@
import { ABSENT, expect, haveResource, haveResourceLike } from '@aws-cdk/assert';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';
Expand Down Expand Up @@ -327,72 +325,6 @@ export = {
test.done();
},

'integration "credentialsRole" and "credentialsPassthrough" are mutually exclusive'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const api = new apigw.RestApi(stack, 'test-api', { deploy: false });
const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') });

// WHEN
const integration = new apigw.Integration({
type: apigw.IntegrationType.AWS_PROXY,
options: {
credentialsPassthrough: true,
credentialsRole: role,
},
});

// THEN
test.throws(() => api.root.addMethod('GET', integration), /'credentialsPassthrough' and 'credentialsRole' are mutually exclusive/);
test.done();
},

'integration connectionType VpcLink requires vpcLink to be set'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const api = new apigw.RestApi(stack, 'test-api', { deploy: false });

// WHEN
const integration = new apigw.Integration({
type: apigw.IntegrationType.HTTP_PROXY,
integrationHttpMethod: 'ANY',
options: {
connectionType: apigw.ConnectionType.VPC_LINK,
},
});

// THEN
test.throws(() => api.root.addMethod('GET', integration), /'connectionType' of VPC_LINK requires 'vpcLink' prop to be set/);
test.done();
},

'connectionType of INTERNET and vpcLink are mutually exclusive'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const api = new apigw.RestApi(stack, 'test-api', { deploy: false });
const vpc = new ec2.Vpc(stack, 'VPC');
const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', {
vpc,
});
const link = new apigw.VpcLink(stack, 'link', {
targets: [nlb],
});

// WHEN
const integration = new apigw.Integration({
type: apigw.IntegrationType.HTTP_PROXY,
integrationHttpMethod: 'ANY',
options: {
connectionType: apigw.ConnectionType.INTERNET,
vpcLink: link,
},
});

// THEN
test.throws(() => api.root.addMethod('GET', integration), /cannot set 'vpcLink' where 'connectionType' is INTERNET/);
test.done();
},

'methodResponse set one or more method responses via options'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
Expand Down