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

feat(apigateway): autodetermine the private integration uri #10730

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,11 @@ const integration = new apigw.Integration({
});
```

The uri for the private integration, in the case of a VpcLink, will be set to the DNS name of
the VPC Link's NLB. If the VPC Link has multiple NLBs or the VPC Link is imported or the DNS
name cannot be determined for any other reason, the user is expected to specify the `uri`
property.

Any existing `VpcLink` resource can be imported into the CDK app via the `VpcLink.fromVpcLinkId()`.

```ts
Expand Down
33 changes: 29 additions & 4 deletions packages/@aws-cdk/aws-apigateway/lib/integration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as iam from '@aws-cdk/aws-iam';
import { Lazy } from '@aws-cdk/core';
import { Method } from './method';
import { IVpcLink } from './vpc-link';
import { IVpcLink, VpcLink } from './vpc-link';

export interface IntegrationOptions {
/**
Expand Down Expand Up @@ -90,7 +91,7 @@ export interface IntegrationOptions {

/**
* The type of network connection to the integration endpoint.
* @default ConnectionType.Internet
* @default - ConnectionType.VPC_LINK if `vpcLink` property is configured; ConnectionType.Internet otherwise.
*/
readonly connectionType?: ConnectionType;

Expand Down Expand Up @@ -199,10 +200,34 @@ export class Integration {
* being integrated, access the REST API object, method ARNs, etc.
*/
public bind(_method: Method): IntegrationConfig {
let uri = this.props.uri;
const options = this.props.options;

if (options?.connectionType === ConnectionType.VPC_LINK && uri === undefined) {
uri = Lazy.stringValue({
nija-at marked this conversation as resolved.
Show resolved Hide resolved
// needs to be a lazy since the targets can be added to the VpcLink construct after initialization.
produce: () => {
const vpcLink = options.vpcLink;
if (vpcLink instanceof VpcLink) {
const targets = vpcLink._targetDnsNames;
if (targets.length > 1) {
throw new Error("'uri' is required when there are more than one NLBs in the VPC Link");
} else {
return `http://${targets[0]}`;
}
} else {
throw new Error("'uri' is required when the 'connectionType' is VPC_LINK");
}
},
});
}
return {
options: this.props.options,
options: {
...options,
connectionType: options?.vpcLink ? ConnectionType.VPC_LINK : options?.connectionType,
},
type: this.props.type,
uri: this.props.uri,
uri,
integrationHttpMethod: this.props.integrationHttpMethod,
};
}
Expand Down
16 changes: 12 additions & 4 deletions packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class VpcLink extends Resource implements IVpcLink {
*/
public readonly vpcLinkId: string;

private readonly targets = new Array<elbv2.INetworkLoadBalancer>();
private readonly _targets = new Array<elbv2.INetworkLoadBalancer>();

constructor(scope: Construct, id: string, props: VpcLinkProps = {}) {
super(scope, id, {
Expand All @@ -83,17 +83,25 @@ export class VpcLink extends Resource implements IVpcLink {
}

public addTargets(...targets: elbv2.INetworkLoadBalancer[]) {
this.targets.push(...targets);
this._targets.push(...targets);
}

/**
* Return the list of DNS names from the target NLBs.
* @internal
* */
public get _targetDnsNames(): string[] {
nija-at marked this conversation as resolved.
Show resolved Hide resolved
return this._targets.map(t => t.loadBalancerDnsName);
}

protected validate(): string[] {
if (this.targets.length === 0) {
if (this._targets.length === 0) {
return ['No targets added to vpc link'];
}
return [];
}

private renderTargets() {
return this.targets.map(nlb => nlb.loadBalancerArn);
return this._targets.map(nlb => nlb.loadBalancerArn);
}
}
140 changes: 140 additions & 0 deletions packages/@aws-cdk/aws-apigateway/test/test.integration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ABSENT, expect, 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';
Expand Down Expand Up @@ -33,6 +34,89 @@ export = {
test.done();
},

'uri is self determined from the NLB'(test: Test) {
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],
});
const api = new apigw.RestApi(stack, 'restapi');
const integration = new apigw.Integration({
type: apigw.IntegrationType.HTTP_PROXY,
integrationHttpMethod: 'ANY',
options: {
connectionType: apigw.ConnectionType.VPC_LINK,
vpcLink: link,
},
});
api.root.addMethod('GET', integration);

expect(stack).to(haveResourceLike('AWS::ApiGateway::Method', {
Integration: {
Uri: {
'Fn::Join': [
'',
[
'http://',
{
'Fn::GetAtt': [
'NLB55158F82',
'DNSName',
],
},
],
],
},
},
}));

test.done();
},

'uri must be set for VpcLink with multiple NLBs'(test: Test) {
const app = new cdk.App();
const stack = new cdk.Stack(app);
const vpc = new ec2.Vpc(stack, 'VPC');
const nlb1 = new elbv2.NetworkLoadBalancer(stack, 'NLB1', { vpc });
const nlb2 = new elbv2.NetworkLoadBalancer(stack, 'NLB2', { vpc });
const link = new apigw.VpcLink(stack, 'link', {
targets: [nlb1, nlb2],
});
const api = new apigw.RestApi(stack, 'restapi');
const integration = new apigw.Integration({
type: apigw.IntegrationType.HTTP_PROXY,
integrationHttpMethod: 'ANY',
options: {
connectionType: apigw.ConnectionType.VPC_LINK,
nija-at marked this conversation as resolved.
Show resolved Hide resolved
vpcLink: link,
},
});
api.root.addMethod('GET', integration);
test.throws(() => app.synth(), /'uri' is required when there are more than one NLBs in the VPC Link/);

test.done();
},

'uri must be set when using an imported VpcLink'(test: Test) {
const app = new cdk.App();
const stack = new cdk.Stack(app);
const link = apigw.VpcLink.fromVpcLinkId(stack, 'link', 'vpclinkid');
const api = new apigw.RestApi(stack, 'restapi');
const integration = new apigw.Integration({
type: apigw.IntegrationType.HTTP_PROXY,
integrationHttpMethod: 'ANY',
options: {
connectionType: apigw.ConnectionType.VPC_LINK,
vpcLink: link,
},
});
api.root.addMethod('GET', integration);
test.throws(() => app.synth(), /'uri' is required when the 'connectionType' is VPC_LINK/);

test.done();
},

'connectionType of INTERNET and vpcLink are mutually exclusive'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
Expand All @@ -55,4 +139,60 @@ export = {
}), /cannot set 'vpcLink' where 'connectionType' is INTERNET/);
test.done();
},

'connectionType is ABSENT when vpcLink is not specified'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const api = new apigw.RestApi(stack, 'restapi');

// WHEN
const integration = new apigw.Integration({
type: apigw.IntegrationType.HTTP_PROXY,
integrationHttpMethod: 'ANY',
});
api.root.addMethod('ANY', integration);

// THEN
expect(stack).to(haveResourceLike('AWS::ApiGateway::Method', {
HttpMethod: 'ANY',
Integration: {
ConnectionType: ABSENT,
},
}));

test.done();
},

'connectionType defaults to VPC_LINK if vpcLink is configured'(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],
});
const api = new apigw.RestApi(stack, 'restapi');

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

// THEN
expect(stack).to(haveResourceLike('AWS::ApiGateway::Method', {
HttpMethod: 'ANY',
Integration: {
ConnectionType: 'VPC_LINK',
},
}));

test.done();
},
};