From d10fe67664f01db568da7f81af12ff647e75fa05 Mon Sep 17 00:00:00 2001 From: nirvana124 Date: Tue, 17 Mar 2020 16:18:49 +0530 Subject: [PATCH] feat(ec2): availabilityZone is optional when importing subnet availabilityZone attribute inside SubnetAttributes is only used for subnet selection. AZ restrictions are not mandatory so making availabilityZone as optional. Also as AZ is optional we should be able to search subnet using only subnet id. Added fromSubnetId as well. fixes #6607 --- packages/@aws-cdk/aws-ec2/README.md | 26 ++++++++++- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 27 +++++++++-- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 52 ++++++++++++++++++++++ 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index c4ff1c03f1939..50e76e8c99af2 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -560,7 +560,7 @@ new ec2.FlowLog(this, 'FlowLog', { ## User Data User data enables you to run a script when your instances start up. In order to configure these scripts you can add commands directly to the script or you can use the UserData's convenience functions to aid in the creation of your script. - + A user data could be configured to run a script found in an asset through the following: ```ts const asset = new Asset(this, 'Asset', {path: path.join(__dirname, 'configure.sh')}); @@ -576,4 +576,26 @@ instance.userData.addExecuteFileCommand({ arguments: '--verbose -y' }); asset.grantRead( instance.role ); -``` \ No newline at end of file +``` + +## Importing existing subnet + +To import an existing Subnet, call `Subnet.fromSubnetAttributes()` or +`Subnet.fromSubnetId()`. Only if you supply the subnet's Availability Zone +and Route Table Ids when calling `Subnet.fromSubnetAttributes()` will you be +able to use the CDK features that use these values (such as selecting one +subnet per AZ). + +Importing an existing subnet looks like this: + +```ts +// Supply all properties +const subnet = Subnet.fromSubnetAttributes(this, 'SubnetFromAttributes', { + subnetId: 's-1234', + availabilityZone: 'pub-az-4465', + routeTableId: 'rt-145' +}); + +// Supply only subnet id +const subnet = Subnet.fromSubnetId(this, 'SubnetFromId', 's-1234'); +``` \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 78cb95d7cfe0f..a99a9683be2e1 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -563,8 +563,10 @@ export interface VpcAttributes { export interface SubnetAttributes { /** * The Availability Zone the subnet is located in + * + * @default - No AZ information, cannot use AZ selection features */ - readonly availabilityZone: string; + readonly availabilityZone?: string; /** * The subnetId for this particular subnet @@ -573,6 +575,8 @@ export interface SubnetAttributes { /** * The ID of the route table for this particular subnet + * + * @default - No route table information, cannot create VPC endpoints */ readonly routeTableId?: string; } @@ -1316,6 +1320,15 @@ export class Subnet extends Resource implements ISubnet { return new ImportedSubnet(scope, id, attrs); } + /** + * Import existing subnet from id. + */ + // tslint:disable:no-shadowed-variable + public static fromSubnetId(scope: Construct, id: string, subnetId: string): ISubnet { + return this.fromSubnetAttributes(scope, id, { subnetId }); + } + // tslint:enable:no-shadowed-variable + /** * The Availability Zone the subnet is located in */ @@ -1770,9 +1783,9 @@ function tap(x: T, fn: (x: T) => void): T { class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivateSubnet { public readonly internetConnectivityEstablished: IDependable = new ConcreteDependable(); - public readonly availabilityZone: string; public readonly subnetId: string; public readonly routeTable: IRouteTable; + private readonly _availabilityZone?: string; constructor(scope: Construct, id: string, attrs: SubnetAttributes) { super(scope, id); @@ -1785,7 +1798,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat scope.node.addWarning(`No routeTableId was provided to the subnet ${ref}. Attempting to read its .routeTable.routeTableId will return null/undefined. (More info: https://github.com/aws/aws-cdk/pull/3171)`); } - this.availabilityZone = attrs.availabilityZone; + this._availabilityZone = attrs.availabilityZone; this.subnetId = attrs.subnetId; this.routeTable = { // Forcing routeTableId to pretend non-null to maintain backwards-compatibility. See https://github.com/aws/aws-cdk/pull/3171 @@ -1793,6 +1806,14 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat }; } + public get availabilityZone(): string { + if (!this._availabilityZone) { + // tslint:disable-next-line: max-line-length + throw new Error("You cannot reference a Subnet's availability zone if it was not supplied. Add the availabilityZone when importing using Subnet.fromSubnetAttributes()"); + } + return this._availabilityZone; + } + public associateNetworkAcl(id: string, networkAcl: INetworkAcl): void { const scope = Construct.isConstruct(networkAcl) ? networkAcl : this; const other = Construct.isConstruct(networkAcl) ? this : networkAcl; diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index b1149592198fa..f6a7484d0e40f 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -1067,6 +1067,58 @@ export = { test.deepEqual(subnetIds.length, 1); test.deepEqual(subnetIds[0], subnet.subnetId); test.done(); + }, + + 'subnet created from subnetId'(test: Test) { + // GIVEN + const stack = getTestStack(); + + // WHEN + const subnet = Subnet.fromSubnetId(stack, 'subnet1', 'pub-1'); + + // THEN + test.deepEqual(subnet.subnetId, 'pub-1'); + test.done(); + }, + + 'Referencing AZ throws error when subnet created from subnetId'(test: Test) { + // GIVEN + const stack = getTestStack(); + + // WHEN + const subnet = Subnet.fromSubnetId(stack, 'subnet1', 'pub-1'); + + // THEN + // tslint:disable-next-line: max-line-length + test.throws(() => subnet.availabilityZone, "You cannot reference a Subnet's availability zone if it was not supplied. Add the availabilityZone when importing using Subnet.fromSubnetAttributes()"); + test.done(); + }, + + 'Referencing AZ throws error when subnet created from attributes without az'(test: Test) { + // GIVEN + const stack = getTestStack(); + + // WHEN + const subnet = Subnet.fromSubnetAttributes(stack, 'subnet1', { subnetId : 'pub-1', availabilityZone: '' }); + + // THEN + test.deepEqual(subnet.subnetId, 'pub-1'); + // tslint:disable-next-line: max-line-length + test.throws(() => subnet.availabilityZone, "You cannot reference a Subnet's availability zone if it was not supplied. Add the availabilityZone when importing using Subnet.fromSubnetAttributes()"); + test.done(); + }, + + 'AZ have value when subnet created from attributes with az'(test: Test) { + // GIVEN + const stack = getTestStack(); + + // WHEN + const subnet = Subnet.fromSubnetAttributes(stack, 'subnet1', { subnetId : 'pub-1', availabilityZone: 'az-1234' }); + + // THEN + test.deepEqual(subnet.subnetId, 'pub-1'); + test.deepEqual(subnet.availabilityZone, 'az-1234'); + test.done(); } },