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(ecs): vpc link for api gatway and load balanced services #1541

Merged
merged 3 commits into from
Jan 16, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-apigateway/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './deployment';
export * from './stage';
export * from './integrations';
export * from './lambda-api';
export * from './vpc-link';

// AWS::ApiGateway CloudFormation Resources:
export * from './apigateway.generated';
25 changes: 25 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/integration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import iam = require('@aws-cdk/aws-iam');
import { Method } from './method';
import { VpcLink } from './vpc-link';

export interface IntegrationOptions {
/**
Expand Down Expand Up @@ -93,6 +94,18 @@ export interface IntegrationOptions {
* @see http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
*/
selectionPattern?: string;

/**
* The type of network connection to the integration endpoint.
* @default ConnectionType.Internet
*/
connectionType?: ConnectionType;

/**
* The VpcLink used for the integration.
* Required if connectionType is VPC_LINK
*/
vpcLink?: VpcLink;
}

export interface IntegrationProps {
Expand Down Expand Up @@ -217,6 +230,18 @@ export enum PassthroughBehavior {
WhenNoTemplates = 'WHEN_NO_TEMPLATES'
}

export enum ConnectionType {
/**
* For connections through the public routable internet
*/
Internet = 'INTERNET',

/**
* For private connections between API Gateway and a network load balancer in a VPC
*/
VpcLink = 'VPC_LINK'
}

export interface IntegrationResponse {
/**
* The status code that API Gateway uses to map the integration response to
Expand Down
10 changes: 9 additions & 1 deletion packages/@aws-cdk/aws-apigateway/lib/method.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import cdk = require('@aws-cdk/cdk');
import { CfnMethod, CfnMethodProps } from './apigateway.generated';
import { Integration } from './integration';
import { ConnectionType, Integration } from './integration';
import { MockIntegration } from './integrations/mock';
import { IRestApiResource } from './resource';
import { RestApi } from './restapi';
Expand Down Expand Up @@ -39,6 +39,7 @@ export interface MethodOptions {
// - RequestModels
// - RequestParameters
// - MethodResponses
requestParameters?: { [param: string]: boolean };
}

export interface MethodProps {
Expand Down Expand Up @@ -91,6 +92,7 @@ export class Method extends cdk.Construct {
apiKeyRequired: options.apiKeyRequired || defaultMethodOptions.apiKeyRequired,
authorizationType: options.authorizationType || defaultMethodOptions.authorizationType || AuthorizationType.None,
authorizerId: options.authorizerId || defaultMethodOptions.authorizerId,
requestParameters: options.requestParameters,
integration: this.renderIntegration(props.integration)
};

Expand Down Expand Up @@ -154,6 +156,10 @@ export class Method extends cdk.Construct {
throw new Error(`'credentialsPassthrough' and 'credentialsRole' are mutually exclusive`);
}

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

if (options.credentialsRole) {
credentials = options.credentialsRole.roleArn;
} else if (options.credentialsPassthrough) {
Expand All @@ -173,6 +179,8 @@ export class Method extends cdk.Construct {
requestTemplates: options.requestTemplates,
passthroughBehavior: options.passthroughBehavior,
integrationResponses: options.integrationResponses,
connectionType: options.connectionType,
connectionId: options.vpcLink ? options.vpcLink.vpcLinkId : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if connectiontype === internet and a vpcLink is supplied. Is that ok?

Copy link
Contributor Author

@dotxlem dotxlem Jan 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs for ConnectionId state:

The ID of the VpcLink used for the integration when connectionType=VPC_LINK and undefined, otherwise.

I took that to mean that it would just be ignored. Probably best to throw an error though to alert the user, since that's probably not what they wanted to happen.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I was getting at. Thanks.

credentials,
};
}
Expand Down
48 changes: 48 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
import cdk = require('@aws-cdk/cdk');
import { CfnVpcLink } from './apigateway.generated';

/**
* Properties for a VpcLink
*/
export interface VpcLinkProps {
/**
* The name used to label and identify the VPC link.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@default automatically generated name

*/
name: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this optional.

If CloudFormation doesn't allow an optional name, you can always default inside the construct to this.uniqueId.


/**
* The description of the VPC link.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@default no description

*/
description?: string;

/**
* The network load balancers of the VPC targeted by the VPC link.
* The network load balancers must be owned by the same AWS account of the API owner.
*/
targets: elbv2.NetworkLoadBalancer[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should take elbv2.INetworkLoadBalancer[].

}

/**
* Define a new VPC Link
* Specifies an API Gateway VPC link for a RestApi to access resources in an Amazon Virtual Private Cloud (VPC).
*/
export class VpcLink extends cdk.Construct {

private readonly cfnResource: CfnVpcLink;

constructor(scope: cdk.Construct, id: string, props: VpcLinkProps) {
super(scope, id);

this.cfnResource = new CfnVpcLink(this, 'Resource', {
name: props.name,
description: props.description,
targetArns: props.targets.map(nlb => nlb.loadBalancerArn)
});
}

public get vpcLinkId() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but we generally just declare a string property on the construct and assign it in the constructor.

return this.cfnResource.vpcLinkId;
}

}
5 changes: 4 additions & 1 deletion packages/@aws-cdk/aws-apigateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"license": "Apache-2.0",
"devDependencies": {
"@aws-cdk/assert": "^0.22.0",
"@aws-cdk/aws-ec2": "^0.22.0",
"cdk-build-tools": "^0.22.0",
"cdk-integ-tools": "^0.22.0",
"cfn2ts": "^0.22.0",
Expand All @@ -63,12 +64,14 @@
"dependencies": {
"@aws-cdk/aws-iam": "^0.22.0",
"@aws-cdk/aws-lambda": "^0.22.0",
"@aws-cdk/aws-elasticloadbalancingv2": "^0.22.0",
"@aws-cdk/cdk": "^0.22.0"
},
"homepage": "https://github.com/awslabs/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-iam": "^0.22.0",
"@aws-cdk/aws-lambda": "^0.22.0",
"@aws-cdk/aws-elasticloadbalancingv2": "^0.22.0",
"@aws-cdk/cdk": "^0.22.0"
},
"engines": {
Expand All @@ -79,4 +82,4 @@
"resource-attribute:@aws-cdk/aws-apigateway.IRestApi.restApiRootResourceId"
]
}
}
}
20 changes: 20 additions & 0 deletions packages/@aws-cdk/aws-apigateway/test/test.method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import iam = require('@aws-cdk/aws-iam');
import cdk = require('@aws-cdk/cdk');
import { Test } from 'nodeunit';
import apigateway = require('../lib');
import { ConnectionType } from '../lib';

export = {
'default setup'(test: Test) {
Expand Down Expand Up @@ -254,4 +255,23 @@ export = {
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 apigateway.RestApi(stack, 'test-api', { deploy: false });

// WHEN
const integration = new apigateway.Integration({
type: apigateway.IntegrationType.HttpProxy,
integrationHttpMethod: 'ANY',
options: {
connectionType: ConnectionType.VpcLink,
}
});

// THEN
test.throws(() => api.root.addMethod('GET', integration), /'connectionType' of VPC_LINK requires 'vpcLink' prop to be set/);
test.done();
},
};
31 changes: 31 additions & 0 deletions packages/@aws-cdk/aws-apigateway/test/test.vpc-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect, haveResourceLike } from '@aws-cdk/assert';
import ec2 = require('@aws-cdk/aws-ec2');
import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
import cdk = require('@aws-cdk/cdk');
import { Test } from 'nodeunit';
import apigateway = require('../lib');

export = {
'default setup'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.VpcNetwork(stack, 'VPC');
const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', {
vpc
});

// WHEN
new apigateway.VpcLink(stack, 'VpcLink', {
name: 'MyLink',
targets: [nlb]
});

// THEN
expect(stack).to(haveResourceLike('AWS::ApiGateway::VpcLink', {
Name: "MyLink",
TargetArns: [ { Ref: "NLB55158F82" } ]
}));

test.done();
},
};
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-ecs/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './fargate/fargate-service';
export * from './fargate/fargate-task-definition';

export * from './linux-parameters';
export * from './load-balanced-service';
export * from './load-balanced-ecs-service';
export * from './load-balanced-fargate-service';
export * from './load-balanced-ecs-service';
Expand Down
63 changes: 6 additions & 57 deletions packages/@aws-cdk/aws-ecs/lib/load-balanced-ecs-service.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
import cdk = require('@aws-cdk/cdk');
import { ICluster } from './cluster';
import { IContainerImage } from './container-image';
import { Ec2Service } from './ec2/ec2-service';
import { Ec2TaskDefinition } from './ec2/ec2-task-definition';
import { LoadBalancedService, LoadBalancedServiceProps } from './load-balanced-service';

/**
* Properties for a LoadBalancedEc2Service
*/
export interface LoadBalancedEc2ServiceProps {
/**
* The cluster where your EC2 service will be deployed
*/
cluster: ICluster;

/**
* The image to start.
*/
image: IContainerImage;

export interface LoadBalancedEc2ServiceProps extends LoadBalancedServiceProps {
/**
* The hard limit (in MiB) of memory to present to the container.
*
Expand All @@ -40,45 +28,20 @@ export interface LoadBalancedEc2ServiceProps {
* At least one of memoryLimitMiB and memoryReservationMiB is required.
*/
memoryReservationMiB?: number;

/**
* The container port of the application load balancer attached to your EC2 service. Corresponds to container port mapping.
*
* @default 80
*/
containerPort?: number;

/**
* Determines whether the Application Load Balancer will be internet-facing
*
* @default true
*/
publicLoadBalancer?: boolean;

/**
* Number of desired copies of running tasks
*
* @default 1
*/
desiredCount?: number;
}

/**
* A single task running on an ECS cluster fronted by a load balancer
*/
export class LoadBalancedEc2Service extends cdk.Construct {
/**
* The load balancer that is fronting the ECS service
*/
public readonly loadBalancer: elbv2.ApplicationLoadBalancer;

export class LoadBalancedEc2Service extends LoadBalancedService {
constructor(scope: cdk.Construct, id: string, props: LoadBalancedEc2ServiceProps) {
super(scope, id);
super(scope, id, props);

const taskDefinition = new Ec2TaskDefinition(this, 'TaskDef', {});

const container = taskDefinition.addContainer('web', {
image: props.image,
environment: props.containerEnvironment,
memoryLimitMiB: props.memoryLimitMiB,
});

Expand All @@ -92,20 +55,6 @@ export class LoadBalancedEc2Service extends cdk.Construct {
taskDefinition,
});

const internetFacing = props.publicLoadBalancer !== undefined ? props.publicLoadBalancer : true;
const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', {
vpc: props.cluster.vpc,
internetFacing
});

this.loadBalancer = lb;

const listener = lb.addListener('PublicListener', { port: 80, open: true });
listener.addTargets('ECS', {
port: 80,
targets: [service]
});

new cdk.Output(this, 'LoadBalancerDNS', { value: lb.dnsName });
this.addServiceAsTarget(service);
}
}
Loading