Skip to content

Commit 4f7818d

Browse files
authored
feat(rds): make VPC optional for serverless Clusters (#17413)
Fixes #17401 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 7441418 commit 4f7818d

File tree

5 files changed

+230
-97
lines changed

5 files changed

+230
-97
lines changed

packages/@aws-cdk/aws-rds/README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ declare const vpc: ec2.Vpc;
639639

640640
const cluster = new rds.ServerlessCluster(this, 'AnotherCluster', {
641641
engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
642-
vpc,
642+
vpc, // this parameter is optional for serverless Clusters
643643
enableDataApi: true, // Optional - will be automatically set if you call grantDataApiAccess()
644644
});
645645

@@ -659,3 +659,11 @@ cluster.grantDataApiAccess(fn);
659659
**Note**: To invoke the Data API, the resource will need to read the secret associated with the cluster.
660660

661661
To learn more about using the Data API, see the [documentation](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html).
662+
663+
### Default VPC
664+
665+
The `vpc` parameter is optional.
666+
667+
If not provided, the cluster will be created in the default VPC of the account and region.
668+
As this VPC is not deployed with AWS CDK, you can't configure the `vpcSubnets`, `subnetGroup` or `securityGroups` of the Aurora Serverless Cluster.
669+
If you want to provide one of `vpcSubnets`, `subnetGroup` or `securityGroups` parameter, please provide a `vpc`.

packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,14 @@ interface ServerlessClusterNewProps {
9999

100100
/**
101101
* The VPC that this Aurora Serverless cluster has been created in.
102+
*
103+
* @default - the default VPC in the account and region will be used
102104
*/
103-
readonly vpc: ec2.IVpc;
105+
readonly vpc?: ec2.IVpc;
104106

105107
/**
106-
* Where to place the instances within the VPC
108+
* Where to place the instances within the VPC.
109+
* If provided, the `vpc` property must also be specified.
107110
*
108111
* @default - the VPC default strategy if not specified.
109112
*/
@@ -129,7 +132,8 @@ interface ServerlessClusterNewProps {
129132
/**
130133
* Security group.
131134
*
132-
* @default - a new security group is created.
135+
* @default - a new security group is created if `vpc` was provided.
136+
* If the `vpc` property was not provided, no VPC security groups will be associated with the DB cluster.
133137
*/
134138
readonly securityGroups?: ec2.ISecurityGroup[];
135139

@@ -143,7 +147,8 @@ interface ServerlessClusterNewProps {
143147
/**
144148
* Existing subnet group for the cluster.
145149
*
146-
* @default - a new subnet group will be created.
150+
* @default - a new subnet group is created if `vpc` was provided.
151+
* If the `vpc` property was not provided, no subnet group will be associated with the DB cluster
147152
*/
148153
readonly subnetGroup?: ISubnetGroup;
149154
}
@@ -351,19 +356,42 @@ abstract class ServerlessClusterNew extends ServerlessClusterBase {
351356
constructor(scope: Construct, id: string, props: ServerlessClusterNewProps) {
352357
super(scope, id);
353358

354-
const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets);
355-
356-
// Cannot test whether the subnets are in different AZs, but at least we can test the amount.
357-
if (subnetIds.length < 2) {
358-
Annotations.of(this).addError(`Cluster requires at least 2 subnets, got ${subnetIds.length}`);
359+
if (props.vpc === undefined) {
360+
if (props.vpcSubnets !== undefined) {
361+
throw new Error('A VPC is required to use vpcSubnets in ServerlessCluster. Please add a VPC or remove vpcSubnets');
362+
}
363+
if (props.subnetGroup !== undefined) {
364+
throw new Error('A VPC is required to use subnetGroup in ServerlessCluster. Please add a VPC or remove subnetGroup');
365+
}
366+
if (props.securityGroups !== undefined) {
367+
throw new Error('A VPC is required to use securityGroups in ServerlessCluster. Please add a VPC or remove securityGroups');
368+
}
359369
}
360370

361-
const subnetGroup = props.subnetGroup ?? new SubnetGroup(this, 'Subnets', {
362-
description: `Subnets for ${id} database`,
363-
vpc: props.vpc,
364-
vpcSubnets: props.vpcSubnets,
365-
removalPolicy: props.removalPolicy === RemovalPolicy.RETAIN ? props.removalPolicy : undefined,
366-
});
371+
let subnetGroup: ISubnetGroup | undefined = props.subnetGroup;
372+
this.securityGroups = props.securityGroups ?? [];
373+
if (props.vpc !== undefined) {
374+
const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets);
375+
376+
// Cannot test whether the subnets are in different AZs, but at least we can test the amount.
377+
if (subnetIds.length < 2) {
378+
Annotations.of(this).addError(`Cluster requires at least 2 subnets, got ${subnetIds.length}`);
379+
}
380+
381+
subnetGroup = props.subnetGroup ?? new SubnetGroup(this, 'Subnets', {
382+
description: `Subnets for ${id} database`,
383+
vpc: props.vpc,
384+
vpcSubnets: props.vpcSubnets,
385+
removalPolicy: props.removalPolicy === RemovalPolicy.RETAIN ? props.removalPolicy : undefined,
386+
});
387+
388+
this.securityGroups = props.securityGroups ?? [
389+
new ec2.SecurityGroup(this, 'SecurityGroup', {
390+
description: 'RDS security group',
391+
vpc: props.vpc,
392+
}),
393+
];
394+
}
367395

368396
if (props.backupRetention) {
369397
const backupRetentionDays = props.backupRetention.toDays();
@@ -379,12 +407,6 @@ abstract class ServerlessClusterNew extends ServerlessClusterBase {
379407
const clusterParameterGroup = props.parameterGroup ?? clusterEngineBindConfig.parameterGroup;
380408
const clusterParameterGroupConfig = clusterParameterGroup?.bindToCluster({});
381409

382-
this.securityGroups = props.securityGroups ?? [
383-
new ec2.SecurityGroup(this, 'SecurityGroup', {
384-
description: 'RDS security group',
385-
vpc: props.vpc,
386-
}),
387-
];
388410

389411
const clusterIdentifier = FeatureFlags.of(this).isEnabled(cxapi.RDS_LOWERCASE_DB_IDENTIFIER)
390412
? props.clusterIdentifier?.toLowerCase()
@@ -395,7 +417,7 @@ abstract class ServerlessClusterNew extends ServerlessClusterBase {
395417
databaseName: props.defaultDatabaseName,
396418
dbClusterIdentifier: clusterIdentifier,
397419
dbClusterParameterGroupName: clusterParameterGroupConfig?.parameterGroupName,
398-
dbSubnetGroupName: subnetGroup.subnetGroupName,
420+
dbSubnetGroupName: subnetGroup?.subnetGroupName,
399421
deletionProtection: defaultDeletionProtection(props.deletionProtection, props.removalPolicy),
400422
engine: props.engine.engineType,
401423
engineVersion: props.engine.engineVersion?.fullVersion,
@@ -476,7 +498,7 @@ export class ServerlessCluster extends ServerlessClusterNew {
476498

477499
public readonly secret?: secretsmanager.ISecret;
478500

479-
private readonly vpc: ec2.IVpc;
501+
private readonly vpc?: ec2.IVpc;
480502
private readonly vpcSubnets?: ec2.SubnetSelection;
481503

482504
private readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication;
@@ -525,6 +547,10 @@ export class ServerlessCluster extends ServerlessClusterNew {
525547
throw new Error('Cannot add single user rotation for a cluster without secret.');
526548
}
527549

550+
if (this.vpc === undefined) {
551+
throw new Error('Cannot add single user rotation for a cluster without VPC.');
552+
}
553+
528554
const id = 'RotationSingleUser';
529555
const existing = this.node.tryFindChild(id);
530556
if (existing) {
@@ -549,6 +575,11 @@ export class ServerlessCluster extends ServerlessClusterNew {
549575
if (!this.secret) {
550576
throw new Error('Cannot add multi user rotation for a cluster without secret.');
551577
}
578+
579+
if (this.vpc === undefined) {
580+
throw new Error('Cannot add multi user rotation for a cluster without VPC.');
581+
}
582+
552583
return new secretsmanager.SecretRotation(this, id, {
553584
...options,
554585
excludeCharacters: options.excludeCharacters ?? DEFAULT_PASSWORD_EXCLUDE_CHARS,
@@ -680,4 +711,4 @@ export class ServerlessClusterFromSnapshot extends ServerlessClusterNew {
680711
this.secret = secret.attach(this);
681712
}
682713
}
683-
}
714+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"Resources": {
3+
"ServerlessDatabaseWithoutVPC93F9A752": {
4+
"Type": "AWS::RDS::DBCluster",
5+
"Properties": {
6+
"Engine": "aurora-mysql",
7+
"DBClusterParameterGroupName": "default.aurora-mysql5.7",
8+
"EngineMode": "serverless",
9+
"MasterUsername": "admin",
10+
"MasterUserPassword": "7959866cacc02c2d243ecfe177464fe6",
11+
"StorageEncrypted": true,
12+
"VpcSecurityGroupIds": []
13+
},
14+
"UpdateReplacePolicy": "Delete",
15+
"DeletionPolicy": "Delete"
16+
}
17+
}
18+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as cdk from '@aws-cdk/core';
2+
import * as rds from '../lib';
3+
4+
const app = new cdk.App();
5+
const stack = new cdk.Stack(app, 'aws-cdk-sls-cluster-no-vpc-integ');
6+
7+
const cluster = new rds.ServerlessCluster(stack, 'Serverless Database Without VPC', {
8+
engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
9+
credentials: {
10+
username: 'admin',
11+
password: cdk.SecretValue.plainText('7959866cacc02c2d243ecfe177464fe6'),
12+
},
13+
removalPolicy: cdk.RemovalPolicy.DESTROY,
14+
});
15+
cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world');
16+
17+
app.synth();

0 commit comments

Comments
 (0)