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 all 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
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
14 changes: 13 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,14 @@ 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.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 @@ -173,6 +183,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
49 changes: 49 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,49 @@
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.
* @default automatically generated name
*/
name?: string;

/**
* The description of the VPC link.
* @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.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 {
/**
* Physical ID of the VpcLink resource
*/
public readonly vpcLinkId: string;

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

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

this.vpcLinkId = 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"
]
}
}
}
49 changes: 49 additions & 0 deletions packages/@aws-cdk/aws-apigateway/test/test.method.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert';
import ec2 = require('@aws-cdk/aws-ec2');
import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
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 +257,50 @@ 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();
},

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

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

// THEN
test.throws(() => api.root.addMethod('GET', integration), /cannot set 'vpcLink' where 'connectionType' is INTERNET/);
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-base';
export * from './load-balanced-ecs-service';
export * from './load-balanced-fargate-service';
export * from './load-balanced-ecs-service';
Expand Down
69 changes: 5 additions & 64 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 { LoadBalancedServiceBase, LoadBalancedServiceBaseProps } from './load-balanced-service-base';

/**
* 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 LoadBalancedServiceBaseProps {
/**
* The hard limit (in MiB) of memory to present to the container.
*
Expand All @@ -40,47 +28,14 @@ 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;

/**
* Environment variables to pass to the container
*
* @default No environment variables
*/
environment?: { [key: string]: string };
}

/**
* 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 LoadBalancedServiceBase {
constructor(scope: cdk.Construct, id: string, props: LoadBalancedEc2ServiceProps) {
super(scope, id);
super(scope, id, props);

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

Expand All @@ -100,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