Skip to content

Commit

Permalink
refractor(apprunner): make VpcConnector its own resource
Browse files Browse the repository at this point in the history
  • Loading branch information
DDynamic committed Mar 25, 2022
1 parent 82aec80 commit 592d9a3
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 112 deletions.
20 changes: 12 additions & 8 deletions packages/@aws-cdk/aws-apprunner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,24 +137,28 @@ See [App Runner IAM Roles](https://docs.aws.amazon.com/apprunner/latest/dg/secur

## VPC Connector

To associate an App Runner service with a custom VPC, define `vpcConnector` for the service, optionally specifying security groups and a name.
To associate an App Runner service with a custom VPC, define `vpcConnector` for the service.

```ts
const vpc = new ec2.Vpc(stack, 'Vpc', {
import * as ec2 from '@aws-cdk/aws-ec2';

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

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

const vpcConnector = new apprunner.VpcConnector(this, 'VpcConnector', {
subnets: vpc.publicSubnets,
securityGroups: [securityGroup],
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: {
subnets: vpc.publicSubnets,
securityGroups: [securityGroup],
name: 'MyVpcConnector',
},
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';
40 changes: 5 additions & 35 deletions packages/@aws-cdk/aws-apprunner/lib/service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecr from '@aws-cdk/aws-ecr';
import * as assets from '@aws-cdk/aws-ecr-assets';
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnService, CfnVpcConnector } from './apprunner.generated';
import { CfnService } from './apprunner.generated';
import { IVpcConnector } from './vpc-connector';

/**
* The image repository types
Expand Down Expand Up @@ -523,7 +523,7 @@ export interface ServiceProps {
*
* @default - no VPC connector, uses the DEFAULT egress type instead
*/
readonly vpcConnector?: VpcConnector;
readonly vpcConnector?: IVpcConnector;
}

/**
Expand Down Expand Up @@ -650,30 +650,6 @@ export class GitHubConnection {
}
}

/**
* Represents an App Runner VPC Connector.
*/
export interface VpcConnector {
/**
* A list of IDs of security groups that App Runner should use for access to AWS resources under the specified subnets.
*
* @default - the default security group of the VPC which allows all outbound traffic.
*/
readonly securityGroups?: ec2.ISecurityGroup[];

/**
* A list of subnets that App Runner should use when it associates the service with a custom Amazon VPC.
*/
readonly subnets: ec2.ISubnet[];

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

/**
* Attributes for the App Runner Service
*/
Expand Down Expand Up @@ -805,12 +781,6 @@ export class Service extends cdk.Resource {
throw new Error('configurationValues cannot be provided if the ConfigurationSource is Repository');
}

const vpcConnector = this.props.vpcConnector ? new CfnVpcConnector(this, 'VpcConnector', {
subnets: this.props.vpcConnector.subnets.map(subnet => subnet.subnetId),
securityGroups: this.props.vpcConnector.securityGroups?.map(securityGroup => securityGroup.securityGroupId),
vpcConnectorName: this.props.vpcConnector.name,
}) : undefined;

const resource = new CfnService(this, 'Resource', {
instanceConfiguration: {
cpu: props.cpu?.unit,
Expand All @@ -824,8 +794,8 @@ export class Service extends cdk.Resource {
},
networkConfiguration: {
egressConfiguration: {
egressType: vpcConnector ? 'VPC' : 'DEFAULT',
vpcConnectorArn: vpcConnector?.attrVpcConnectorArn,
egressType: this.props.vpcConnector ? 'VPC' : 'DEFAULT',
vpcConnectorArn: this.props.vpcConnector?.vpcConnectorArn,
},
},
});
Expand Down
135 changes: 135 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,135 @@
import * as ec2 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 {
/**
* A list of IDs of security groups that App Runner should use for access to AWS resources under the specified subnets.
*
* @default - the default security group of the VPC which allows all outbound traffic.
*/
readonly securityGroups?: ec2.ISecurityGroup[];

/**
* A list of subnets that App Runner should use when it associates the service with a custom Amazon VPC.
*/
readonly subnets: ec2.ISubnet[];

/**
* 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;
}

/**
* Represents the App Runner VPC Connector.
*/
export interface IVpcConnector extends cdk.IResource {
/**
* The Name of the VPC connector.
*/
readonly vpcConnectorName: string;

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

/**
* The App Runner VPC Connector
*/
export class VpcConnector extends cdk.Resource {
/**
* Import from VPC connector name.
*/
public static fromVpcConnectorName(scope: Construct, id: string, vpcConnectorName: string): IVpcConnector {
class Import extends cdk.Resource {
public vpcConnectorName = vpcConnectorName;
public vpcConnectorArn = cdk.Stack.of(this).formatArn({
resource: 'vpcconnector',
service: 'apprunner',
resourceName: vpcConnectorName,
})
}
return new Import(scope, id);
}

/**
* Import from VPC connector attributes.
*/
public static fromServiceAttributes(scope: Construct, id: string, attrs: VpcConnectorAttributes): IVpcConnector {
const vpcConnectorArn = attrs.vpcConnectorArn;
const vpcConnectorName = attrs.vpcConnectorName;
const vpcConnectorRevision = attrs.vpcConnectorRevision;

class Import extends cdk.Resource {
public readonly vpcConnectorArn = vpcConnectorArn
public readonly vpcConnectorName = vpcConnectorName
public readonly vpcConnectorRevision = vpcConnectorRevision
}

return new Import(scope, id);
}
private readonly props: VpcConnectorProps;

/**
* 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;

public constructor(scope: Construct, id: string, props: VpcConnectorProps) {
super(scope, id);

this.props = props;

const resource = new CfnVpcConnector(this, 'VpcConnector', {
subnets: this.props.subnets.map(subnet => subnet.subnetId),
securityGroups: this.props.securityGroups?.map(securityGroup => securityGroup.securityGroupId),
vpcConnectorName: this.props.vpcConnectorName,
});

this.vpcConnectorArn = resource.attrVpcConnectorArn;
this.vpcConnectorRevision = resource.attrVpcConnectorRevision;
this.vpcConnectorName = resource.ref;
}
}
47 changes: 45 additions & 2 deletions packages/@aws-cdk/aws-apprunner/test/integ.service.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@
}
}
},
"Service6VpcConnector7B91ABE1": {
"VpcConnectorCF6E3C30": {
"Type": "AWS::AppRunner::VpcConnector",
"Properties": {
"Subnets": [
Expand Down Expand Up @@ -877,7 +877,34 @@
"EgressType": "VPC",
"VpcConnectorArn": {
"Fn::GetAtt": [
"Service6VpcConnector7B91ABE1",
"VpcConnectorCF6E3C30",
"VpcConnectorArn"
]
}
}
}
}
},
"Service7E1F980B2": {
"Type": "AWS::AppRunner::Service",
"Properties": {
"SourceConfiguration": {
"AuthenticationConfiguration": {},
"ImageRepository": {
"ImageConfiguration": {
"Port": "8000"
},
"ImageIdentifier": "public.ecr.aws/aws-containers/hello-app-runner:latest",
"ImageRepositoryType": "ECR_PUBLIC"
}
},
"InstanceConfiguration": {},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "VPC",
"VpcConnectorArn": {
"Fn::GetAtt": [
"VpcConnectorCF6E3C30",
"VpcConnectorArn"
]
}
Expand Down Expand Up @@ -982,6 +1009,22 @@
]
]
}
},
"URL7": {
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Fn::GetAtt": [
"Service7E1F980B2",
"ServiceUrl"
]
}
]
]
}
}
}
}
32 changes: 25 additions & 7 deletions packages/@aws-cdk/aws-apprunner/test/integ.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecr from '@aws-cdk/aws-ecr';
import * as assets from '@aws-cdk/aws-ecr-assets';
import * as cdk from '@aws-cdk/core';
import { Service, Source, GitHubConnection, ConfigurationSourceType, Runtime } from '../lib';
import { Service, Source, GitHubConnection, ConfigurationSourceType, Runtime, VpcConnector } from '../lib';


const app = new cdk.App();
Expand Down Expand Up @@ -71,24 +71,42 @@ const service5 = new Service(stack, 'Service5', {
});
new cdk.CfnOutput(stack, 'URL5', { value: `https://${service5.serviceUrl}` });

// Scenario 6: Create the service from ECR public using a vpcConnector
// Scenario 6: Create the service from ECR public with a vpcConnector
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', {
subnets: vpc.publicSubnets,
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: {
subnets: vpc.publicSubnets,
securityGroups: [securityGroup],
name: 'MyVpcConnector',
},
vpcConnector,
});
new cdk.CfnOutput(stack, 'URL6', { value: `https://${service6.serviceUrl}` });

// Scenario 7: Create the service from ECR public and assign it to an existing vpcConnector
const service7 = new Service(stack, 'Service7', {
source: Source.fromEcrPublic({
imageConfiguration: {
port: 8000,
},
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
}),
vpcConnector: VpcConnector.fromServiceAttributes(stack, 'ImportedVpcConnector', {
vpcConnectorArn: vpcConnector.vpcConnectorArn,
vpcConnectorName: vpcConnector.vpcConnectorName,
vpcConnectorRevision: vpcConnector.vpcConnectorRevision,
}),
});
new cdk.CfnOutput(stack, 'URL7', { value: `https://${service7.serviceUrl}` });
Loading

0 comments on commit 592d9a3

Please sign in to comment.