Skip to content

Commit db345c5

Browse files
authored
chore(VpcV2): add default name tag for VPC and components (#32360)
### Issue # (if applicable) Closes [RFC#507](https://github.com/aws/aws-cdk-rfcs/blob/main/text/0507-subnets.md). Tracking (#30762 ) ### Reason for this change Added Name tag for VPC and related components. ### Description of changes Tag fields not supported in CFN schema for Route and Egress-only-IGW. ### Description of how you validated changes Added unit test and integration tests. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 107eed3 commit db345c5

File tree

63 files changed

+33938
-209
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+33938
-209
lines changed

packages/@aws-cdk/aws-ec2-alpha/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,3 +645,22 @@ SubnetV2.fromSubnetV2Attributes(this, 'ImportedSubnet', {
645645
```
646646

647647
By importing existing VPCs and subnets, you can easily integrate your existing AWS infrastructure with new resources created through CDK. This is particularly useful when you need to work with pre-existing network configurations or when you're migrating existing infrastructure to CDK.
648+
649+
### Tagging VPC and its components
650+
651+
By default, when a resource name is given to the construct, it automatically adds a tag with the key `Name` and the value set to the provided resource name. To add additional custom tags, use the Tag Manager, like this: `Tags.of(myConstruct).add('key', 'value');`.
652+
653+
For example, if the `vpcName` is set to `TestVpc`, the following code will add a tag to the VPC with `key: Name` and `value: TestVpc`.
654+
655+
```ts
656+
657+
const vpc = new VpcV2(this, 'VPC-integ-test-tag', {
658+
primaryAddressBlock: IpAddresses.ipv4('10.1.0.0/16'),
659+
enableDnsHostnames: true,
660+
enableDnsSupport: true,
661+
vpcName: 'CDKintegTestVPC',
662+
});
663+
664+
// Add custom tags if needed
665+
Tags.of(vpc).add('Environment', 'Production');
666+
```

packages/@aws-cdk/aws-ec2-alpha/lib/ipam.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CfnIPAM, CfnIPAMPool, CfnIPAMPoolCidr, CfnIPAMScope } from 'aws-cdk-lib/aws-ec2';
22
import { Construct } from 'constructs';
3-
import { Lazy, Names, Resource, Stack } from 'aws-cdk-lib';
3+
import { Lazy, Names, Resource, Stack, Tags } from 'aws-cdk-lib';
44

55
/**
66
* Represents the address family for IP addresses in an IPAM pool.
@@ -136,8 +136,17 @@ export interface PoolOptions {
136136
* @default - required in case of an IPv6, throws an error if not provided.
137137
*/
138138
readonly awsService?: AwsServiceName;
139+
140+
/**
141+
* IPAM Pool resource name to be used for tagging
142+
*
143+
* @default - autogenerated by CDK if not provided
144+
*/
145+
readonly ipamPoolName?: string;
139146
}
140147

148+
const NAME_TAG: string = 'Name';
149+
141150
/**
142151
* Properties for creating an IPAM pool.
143152
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-ipampool.html
@@ -147,13 +156,6 @@ interface IpamPoolProps extends PoolOptions {
147156
* Scope id where pool needs to be created
148157
*/
149158
readonly ipamScopeId: string;
150-
151-
/**
152-
* IPAM resource name
153-
*
154-
* @default - autogenerated by CDK if not provided
155-
*/
156-
readonly ipamPoolName?: string;
157159
}
158160

159161
/**
@@ -343,6 +345,11 @@ class IpamPool extends Resource implements IIpamPool {
343345
throw new Error('awsService is required when addressFamily is set to ipv6');
344346
}
345347

348+
//Add tags to the IPAM Pool if name is provided
349+
if (props.ipamPoolName) {
350+
Tags.of(this).add(NAME_TAG, props.ipamPoolName);
351+
}
352+
346353
this._ipamPool = new CfnIPAMPool(this, id, {
347354
addressFamily: props.addressFamily,
348355
provisionedCidrs: props.ipv4ProvisionedCidrs?.map(cidr => ({ cidr })),
@@ -413,6 +420,7 @@ class IpamScope extends Resource implements IIpamScopeBase {
413420
this._ipamScope = new CfnIPAMScope(scope, 'IpamScope', {
414421
ipamId: props.ipamId,
415422
});
423+
Tags.of(this._ipamScope).add(NAME_TAG, props.ipamScopeName ?? 'CustomIpamScope');
416424
this.scopeId = this._ipamScope.attrIpamScopeId;
417425
this.scopeType = IpamScopeType.CUSTOM;
418426
this.scope = scope;
@@ -494,14 +502,24 @@ export class Ipam extends Resource {
494502
*/
495503
public readonly scopes: IIpamScopeBase[] = [];
496504

505+
/**
506+
* IPAM name to be used for tagging
507+
* @default no tag specified
508+
* @attribute IpamName
509+
*/
510+
public readonly ipamName?: string;
511+
497512
constructor(scope: Construct, id: string, props?: IpamProps) {
498513
super(scope, id);
499-
514+
if (props?.ipamName) {
515+
Tags.of(this).add(NAME_TAG, props.ipamName);
516+
}
500517
if (!props?.operatingRegion && !Stack.of(this).region) {
501518
throw new Error('Please provide at least one operating region');
502519
}
503520

504521
this.operatingRegions = props?.operatingRegion ?? [Stack.of(this).region];
522+
this.ipamName = props?.ipamName;
505523

506524
this._ipam = new CfnIPAM(this, 'Ipam', {
507525
operatingRegions: this.operatingRegions ? this.operatingRegions.map(region => ({ regionName: region })) : [],
@@ -560,7 +578,7 @@ function createIpamPool(
560578
}
561579

562580
return new IpamPool(scope, id, {
563-
ipamPoolName: id,
581+
ipamPoolName: poolOptions.ipamPoolName,
564582
addressFamily: poolOptions.addressFamily,
565583
ipv4ProvisionedCidrs: poolOptions.ipv4ProvisionedCidrs,
566584
ipamScopeId: scopeId,

packages/@aws-cdk/aws-ec2-alpha/lib/route.ts

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CfnEIP, CfnEgressOnlyInternetGateway, CfnInternetGateway, CfnNatGateway, CfnVPCPeeringConnection, CfnRoute, CfnRouteTable, CfnVPCGatewayAttachment, CfnVPNGateway, CfnVPNGatewayRoutePropagation, GatewayVpcEndpoint, IRouteTable, IVpcEndpoint, RouterType } from 'aws-cdk-lib/aws-ec2';
22
import { Construct, IDependable } from 'constructs';
3-
import { Annotations, Duration, IResource, Resource } from 'aws-cdk-lib/core';
3+
import { Annotations, Duration, IResource, Resource, Tags } from 'aws-cdk-lib/core';
44
import { IVpcV2, VPNGatewayV2Options } from './vpc-v2-base';
55
import { NetworkUtils, allRouteTableIds, CidrBlock } from './util';
66
import { ISubnetV2 } from './subnet-v2';
@@ -209,6 +209,11 @@ export interface VPCPeeringConnectionProps extends VPCPeeringConnectionOptions {
209209
readonly requestorVpc: IVpcV2;
210210
}
211211

212+
/**
213+
* Name tag constant
214+
*/
215+
const NAME_TAG: string = 'Name';
216+
212217
/**
213218
* Creates an egress-only internet gateway
214219
* @resource AWS::EC2::EgressOnlyInternetGateway
@@ -232,6 +237,9 @@ export class EgressOnlyInternetGateway extends Resource implements IRouteTarget
232237
constructor(scope: Construct, id: string, props: EgressOnlyInternetGatewayProps) {
233238
super(scope, id);
234239

240+
if (props.egressOnlyInternetGatewayName) {
241+
Tags.of(this).add(NAME_TAG, props.egressOnlyInternetGatewayName);
242+
}
235243
this.routerType = RouterType.EGRESS_ONLY_INTERNET_GATEWAY;
236244

237245
this.resource = new CfnEgressOnlyInternetGateway(this, 'EIGW', {
@@ -279,6 +287,10 @@ export class InternetGateway extends Resource implements IRouteTarget {
279287
this.routerTargetId = this.resource.attrInternetGatewayId;
280288
this.vpcId = props.vpc.vpcId;
281289

290+
if (props.internetGatewayName) {
291+
Tags.of(this).add(NAME_TAG, props.internetGatewayName);
292+
}
293+
282294
new CfnVPCGatewayAttachment(this, 'GWAttachment', {
283295
vpcId: this.vpcId,
284296
internetGatewayId: this.routerTargetId,
@@ -322,7 +334,9 @@ export class VPNGatewayV2 extends Resource implements IRouteTarget {
322334
private readonly _routePropagation: CfnVPNGatewayRoutePropagation;
323335

324336
constructor(scope: Construct, id: string, props: VPNGatewayV2Props) {
325-
super(scope, id);
337+
super(scope, id, {
338+
physicalName: props.vpnGatewayName,
339+
});
326340

327341
this.routerType = RouterType.GATEWAY;
328342

@@ -340,6 +354,10 @@ export class VPNGatewayV2 extends Resource implements IRouteTarget {
340354
vpnGatewayId: this.resource.attrVpnGatewayId,
341355
});
342356

357+
if (props.vpnGatewayName) {
358+
Tags.of(this).add(NAME_TAG, props.vpnGatewayName);
359+
}
360+
343361
// Propagate routes on route tables associated with the right subnets
344362
const vpnRoutePropagation = props.vpnRoutePropagation ?? [];
345363
const subnets = vpnRoutePropagation.map(s => props.vpc.selectSubnets(s).subnets).flat();
@@ -365,15 +383,22 @@ export class VPNGatewayV2 extends Resource implements IRouteTarget {
365383
* @resource AWS::EC2::NatGateway
366384
*/
367385
export class NatGateway extends Resource implements IRouteTarget {
386+
387+
/**
388+
* Id of the NatGateway
389+
* @attribute
390+
*/
391+
public readonly natGatewayId: string;
392+
368393
/**
369394
* The type of router used in the route.
370395
*/
371-
readonly routerType: RouterType;
396+
public readonly routerType: RouterType;
372397

373398
/**
374399
* The ID of the route target.
375400
*/
376-
readonly routerTargetId: string;
401+
public readonly routerTargetId: string;
377402

378403
/**
379404
* Indicates whether the NAT gateway supports public or private connectivity.
@@ -409,6 +434,10 @@ export class NatGateway extends Resource implements IRouteTarget {
409434
}
410435
}
411436

437+
if (props.natGatewayName) {
438+
Tags.of(this).add(NAME_TAG, props?.natGatewayName);
439+
}
440+
412441
// If user does not provide EIP, generate one for them
413442
var aId: string | undefined;
414443
if (this.connectivityType === NatConnectivityType.PUBLIC) {
@@ -429,6 +458,7 @@ export class NatGateway extends Resource implements IRouteTarget {
429458
secondaryAllocationIds: props.secondaryAllocationIds,
430459
...props,
431460
});
461+
this.natGatewayId = this.resource.attrNatGatewayId;
432462

433463
this.routerTargetId = this.resource.attrNatGatewayId;
434464
this.node.defaultChild = this.resource;
@@ -476,6 +506,9 @@ export class VPCPeeringConnection extends Resource implements IRouteTarget {
476506
if (overlap) {
477507
throw new Error('CIDR block should not overlap with each other for establishing a peering connection');
478508
}
509+
if (props.vpcPeeringConnectionName) {
510+
Tags.of(this).add(NAME_TAG, props.vpcPeeringConnectionName);
511+
}
479512

480513
this.resource = new CfnVPCPeeringConnection(this, 'VPCPeeringConnection', {
481514
vpcId: props.requestorVpc.vpcId,
@@ -700,7 +733,6 @@ export class Route extends Resource implements IRouteV2 {
700733
throw new Error('Egress only internet gateway does not support IPv4 routing');
701734
}
702735
this.targetRouterType = this.target.gateway ? this.target.gateway.routerType : RouterType.VPC_ENDPOINT;
703-
704736
// Gateway generates route automatically via its RouteTable, thus we don't need to generate the resource for it
705737
if (!(this.target.endpoint instanceof GatewayVpcEndpoint)) {
706738
this.resource = new CfnRoute(this, 'Route', {
@@ -761,6 +793,9 @@ export class RouteTable extends Resource implements IRouteTable {
761793
this.resource = new CfnRouteTable(this, 'RouteTable', {
762794
vpcId: props.vpc.vpcId,
763795
});
796+
if (props.routeTableName) {
797+
Tags.of(this).add(NAME_TAG, props.routeTableName);
798+
}
764799
this.node.defaultChild = this.resource;
765800

766801
this.routeTableId = this.resource.attrRouteTableId;
@@ -771,12 +806,14 @@ export class RouteTable extends Resource implements IRouteTable {
771806
*
772807
* @param destination The IPv4 or IPv6 CIDR block used for the destination match.
773808
* @param target The gateway or endpoint targeted by the route.
809+
* @param routeName The resource name of the route.
774810
*/
775-
public addRoute(id: string, destination: string, target: RouteTargetType) {
811+
public addRoute(id: string, destination: string, target: RouteTargetType, routeName?: string) {
776812
new Route(this, id, {
777813
routeTable: this,
778814
destination: destination,
779815
target: target,
816+
routeName: routeName,
780817
});
781818
}
782819
}

packages/@aws-cdk/aws-ec2-alpha/lib/subnet-v2.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Resource, Names, Lazy } from 'aws-cdk-lib';
1+
import { Resource, Names, Lazy, Tags } from 'aws-cdk-lib';
22
import { CfnSubnet, CfnSubnetRouteTableAssociation, INetworkAcl, IRouteTable, ISubnet, NetworkAcl, SubnetNetworkAclAssociation, SubnetType } from 'aws-cdk-lib/aws-ec2';
33
import { Construct, DependencyGroup, IDependable } from 'constructs';
44
import { IVpcV2 } from './vpc-v2-base';
@@ -27,6 +27,16 @@ export class IpCidr implements ICidr {
2727
}
2828
}
2929

30+
/**
31+
* Name tag constant
32+
*/
33+
const NAME_TAG: string = 'Name';
34+
35+
/**
36+
* VPC Name tag constant
37+
*/
38+
const VPCNAME_TAG: string = 'VpcName';
39+
3040
/**
3141
* Properties to define subnet for VPC.
3242
*/
@@ -282,12 +292,21 @@ export class SubnetV2 extends Resource implements ISubnetV2 {
282292

283293
this._networkAcl = NetworkAcl.fromNetworkAclId(this, 'Acl', subnet.attrNetworkAclAssociationId);
284294

295+
if (props.subnetName) {
296+
Tags.of(this).add(NAME_TAG, props.subnetName);
297+
}
298+
299+
if (props.vpc.vpcName) {
300+
Tags.of(this).add(VPCNAME_TAG, props.vpc.vpcName);
301+
}
302+
285303
if (props.routeTable) {
286304
this._routeTable = props.routeTable;
287305
} else {
288306
// Assigning a default route table
289307
this._routeTable = new RouteTable(this, 'RouteTable', {
290308
vpc: props.vpc,
309+
routeTableName: 'DefaultCDKRouteTable',
291310
});
292311
}
293312

0 commit comments

Comments
 (0)