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(apprunner): VpcConnector construct #20471

Merged
merged 12 commits into from
May 25, 2022
26 changes: 26 additions & 0 deletions packages/@aws-cdk/aws-apprunner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,29 @@ ECR image repositories (but not for ECR Public repositories). If not defined, a
when required.

See [App Runner IAM Roles](https://docs.aws.amazon.com/apprunner/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles) for more details.

## VPC Connector

To associate an App Runner service with a custom VPC, define `vpcConnector` for the service.

```ts
import * as ec2 from '@aws-cdk/aws-ec2';

const vpc = new ec2.Vpc(this, 'Vpc', {
cidr: '10.0.0.0/16',
});

const vpcConnector = new apprunner.VpcConnector(this, 'VpcConnector', {
vpc,
vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }),
vpcConnectorName: 'MyVpcConnector',
});

new apprunner.Service(this, 'Service', {
source: apprunner.Source.fromEcrPublic({
imageConfiguration: { port: 8000 },
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
}),
vpcConnector,
});
```
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-apprunner/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// AWS::AppRunner CloudFormation Resources:
export * from './apprunner.generated';
export * from './service';
export * from './vpc-connector';
14 changes: 14 additions & 0 deletions packages/@aws-cdk/aws-apprunner/lib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnService } from './apprunner.generated';
import { IVpcConnector } from './vpc-connector';

/**
* The image repository types
Expand Down Expand Up @@ -524,6 +525,13 @@ export interface ServiceProps {
* @default - auto-generated if undefined.
*/
readonly serviceName?: string;

/**
* Settings for an App Runner VPC connector to associate with the service.
*
* @default - no VPC connector, uses the DEFAULT egress type instead
*/
readonly vpcConnector?: IVpcConnector;
}

/**
Expand Down Expand Up @@ -792,6 +800,12 @@ export class Service extends cdk.Resource {
imageRepository: source.imageRepository ? this.renderImageRepository() : undefined,
codeRepository: source.codeRepository ? this.renderCodeConfiguration() : undefined,
},
networkConfiguration: {
egressConfiguration: {
egressType: this.props.vpcConnector ? 'VPC' : 'DEFAULT',
vpcConnectorArn: this.props.vpcConnector?.vpcConnectorArn,
},
},
});

// grant required privileges for the role
Expand Down
154 changes: 154 additions & 0 deletions packages/@aws-cdk/aws-apprunner/lib/vpc-connector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import { Connections } from '@aws-cdk/aws-ec2';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnVpcConnector } from './apprunner.generated';

/**
* Properties of the AppRunner VPC Connector
*/
export interface VpcConnectorProps {
/**
* The VPC for the VPC Connector.
*/
readonly vpc: ec2.IVpc;

/**
* Where to place the VPC Connector within the VPC.
*
* @default - Private subnets.
*/
readonly vpcSubnets?: ec2.SubnetSelection;

/**
* A list of IDs of security groups that App Runner should use for access to AWS resources under the specified subnets.
*
* @default - a new security group will be created in the specified VPC
*/
readonly securityGroups?: ec2.ISecurityGroup[];

/**
* The name for the VpcConnector.
*
* @default - a name generated by CloudFormation
*/
readonly vpcConnectorName?: string;
}

/**
* Attributes for the App Runner VPC Connector
*/
export interface VpcConnectorAttributes {
/**
* The name of the VPC connector.
*/
readonly vpcConnectorName: string;

/**
* The ARN of the VPC connector.
*/
readonly vpcConnectorArn: string;

/**
* The revision of the VPC connector.
*/
readonly vpcConnectorRevision: number;

/**
* The security groups associated with the VPC connector.
*/
readonly securityGroups: ec2.ISecurityGroup[];
}

/**
* Represents the App Runner VPC Connector.
*/
export interface IVpcConnector extends cdk.IResource, ec2.IConnectable {
/**
* The Name of the VPC connector.
DDynamic marked this conversation as resolved.
Show resolved Hide resolved
* @attribute
*/
readonly vpcConnectorName: string;

/**
* The ARN of the VPC connector.
DDynamic marked this conversation as resolved.
Show resolved Hide resolved
* @attribute
*/
readonly vpcConnectorArn: string;

/**
* The revision of the VPC connector.
* @attribute
*/
readonly vpcConnectorRevision: number;
}

/**
* The App Runner VPC Connector
DDynamic marked this conversation as resolved.
Show resolved Hide resolved
*
* @resource AWS::AppRunner::VpcConnector
*/
export class VpcConnector extends cdk.Resource implements IVpcConnector {
/**
* Import from VPC connector attributes.
*/
public static fromVpcConnectorAttributes(scope: Construct, id: string, attrs: VpcConnectorAttributes): IVpcConnector {
const vpcConnectorArn = attrs.vpcConnectorArn;
const vpcConnectorName = attrs.vpcConnectorName;
const vpcConnectorRevision = attrs.vpcConnectorRevision;
const securityGroups = attrs.securityGroups;

class Import extends cdk.Resource {
public readonly vpcConnectorArn = vpcConnectorArn
public readonly vpcConnectorName = vpcConnectorName
public readonly vpcConnectorRevision = vpcConnectorRevision
public readonly connections = new Connections({ securityGroups });
}

return new Import(scope, id);
}

/**
* The ARN of the VPC connector.
* @attribute
*/
readonly vpcConnectorArn: string;

/**
* The revision of the VPC connector.
* @attribute
*/
readonly vpcConnectorRevision: number;

/**
* The name of the VPC connector.
* @attribute
*/
readonly vpcConnectorName: string;

/**
* Allows specifying security group connections for the VPC connector.
*/
public readonly connections: Connections

public constructor(scope: Construct, id: string, props: VpcConnectorProps) {
super(scope, id, {
physicalName: props.vpcConnectorName,
});

const securityGroups = props.securityGroups?.length ?
DDynamic marked this conversation as resolved.
Show resolved Hide resolved
props.securityGroups
: [new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc })];

const resource = new CfnVpcConnector(this, 'Resource', {
subnets: props.vpc.selectSubnets(props.vpcSubnets).subnetIds,
securityGroups: cdk.Lazy.list({ produce: () => this.connections.securityGroups.map(sg => sg.securityGroupId) }),
vpcConnectorName: this.physicalName,
});

this.vpcConnectorArn = resource.attrVpcConnectorArn;
this.vpcConnectorRevision = resource.attrVpcConnectorRevision;
this.vpcConnectorName = resource.ref;
this.connections = new Connections({ securityGroups });
}
}
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-apprunner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
},
"license": "Apache-2.0",
"devDependencies": {
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/assertions": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
Expand All @@ -91,13 +92,15 @@
"@types/jest": "^27.5.0"
},
"dependencies": {
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/aws-ecr": "0.0.0",
"@aws-cdk/aws-ecr-assets": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
"peerDependencies": {
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/aws-ecr": "0.0.0",
"@aws-cdk/aws-ecr-assets": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import * as cdk from '@aws-cdk/core';
import { Service, Source, VpcConnector } from '../lib';


const app = new cdk.App();

const stack = new cdk.Stack(app, 'integ-apprunner');

// Scenario 6: Create the service from ECR public with a VPC Connector
const vpc = new ec2.Vpc(stack, 'Vpc', {
cidr: '10.0.0.0/16',
});

const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc });

const vpcConnector = new VpcConnector(stack, 'VpcConnector', {
vpc,
vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }),
securityGroups: [securityGroup],
vpcConnectorName: 'MyVpcConnector',
});

const service6 = new Service(stack, 'Service6', {
source: Source.fromEcrPublic({
imageConfiguration: {
port: 8000,
},
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
}),
vpcConnector,
});
new cdk.CfnOutput(stack, 'URL6', { value: `https://${service6.serviceUrl}` });

// Scenario 7: Create the service from ECR public and associate it with an existing VPC Connector

const service7 = new Service(stack, 'Service7', {
source: Source.fromEcrPublic({
imageConfiguration: {
port: 8000,
},
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
}),
vpcConnector: VpcConnector.fromVpcConnectorAttributes(stack, 'ImportedVpcConnector', {
vpcConnectorArn: vpcConnector.vpcConnectorArn,
vpcConnectorName: vpcConnector.vpcConnectorName,
vpcConnectorRevision: vpcConnector.vpcConnectorRevision,
securityGroups: [securityGroup],
}),
});
new cdk.CfnOutput(stack, 'URL7', { value: `https://${service7.serviceUrl}` });
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"17.0.0"}
{"version":"19.0.0"}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
"ImageRepositoryType": "ECR_PUBLIC"
}
},
"InstanceConfiguration": {}
"InstanceConfiguration": {},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "DEFAULT"
}
}
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": "18.0.0",
"version": "19.0.0",
"testCases": {
"aws-apprunner/test/integ.service-ecr-public": {
"integ.service-ecr-public": {
"stacks": [
"integ-apprunner-ecr-public"
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "17.0.0",
"version": "19.0.0",
"artifacts": {
"Tree": {
"type": "cdk:tree",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@
"imageRepositoryType": "ECR_PUBLIC"
}
},
"instanceConfiguration": {}
"instanceConfiguration": {},
"networkConfiguration": {
"egressConfiguration": {
"egressType": "DEFAULT"
}
}
}
},
"constructInfo": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"17.0.0"}
{"version":"19.0.0"}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@
"ImageRepositoryType": "ECR"
}
},
"InstanceConfiguration": {}
"InstanceConfiguration": {},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "DEFAULT"
}
}
}
},
"Service2AccessRole759CA73D": {
Expand Down Expand Up @@ -211,7 +216,12 @@
"ImageRepositoryType": "ECR"
}
},
"InstanceConfiguration": {}
"InstanceConfiguration": {},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "DEFAULT"
}
}
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": "18.0.0",
"version": "19.0.0",
"testCases": {
"aws-apprunner/test/integ.service-ecr": {
"integ.service-ecr": {
"stacks": [
"integ-apprunner"
],
Expand Down
Loading