Skip to content

Commit b864041

Browse files
jogoldrix0rrr
authored andcommitted
feat(rds): add support for database instances (#2187)
Specific classes are exposed for a source database instance (`DatabaseInstance`), an instance created from a snapshot (`DatabaseInstanceFromSnapshot`) and a read replica instance (`DatabaseInstanceReadReplica`). Add construct for option groups and refactor parameter groups. Add basic support for instance event rules. Integration with Secrets Manager and secret rotation. Closes #2075 Closes #1693
1 parent 421bf6d commit b864041

23 files changed

+3453
-334
lines changed

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

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,8 @@
11
## AWS RDS Construct Library
22

3-
The `aws-cdk-rds` package contains Constructs for setting up RDS instances.
4-
5-
> Note: the functionality this package is currently limited, as the CDK team is
6-
> focusing on other use cases first. If your use case is not listed below, you
7-
> will have to use achieve it using CloudFormation resources.
8-
>
9-
> If you would like to help improve the state of this library, Pull Requests are
10-
> welcome.
11-
12-
Supported:
13-
14-
* Clustered databases
15-
16-
Not supported:
17-
18-
* Instance databases
19-
* Setting up from a snapshot
20-
21-
223
### Starting a Clustered Database
234

24-
To set up a clustered database (like Aurora), create an instance of `DatabaseCluster`. You must
5+
To set up a clustered database (like Aurora), define a `DatabaseCluster`. You must
256
always launch a database in a VPC. Use the `vpcSubnets` attribute to control whether
267
your instances will be launched privately or publicly:
278

@@ -45,33 +26,84 @@ By default, the master password will be generated and stored in AWS Secrets Mana
4526
Your cluster will be empty by default. To add a default database upon construction, specify the
4627
`defaultDatabaseName` attribute.
4728

29+
### Starting an Instance Database
30+
To set up a instance database, define a `DatabaseInstance`. You must
31+
always launch a database in a VPC. Use the `vpcSubnets` attribute to control whether
32+
your instances will be launched privately or publicly:
33+
34+
```ts
35+
const instance = new DatabaseInstance(stack, 'Instance', {
36+
engine: rds.DatabaseInstanceEngine.OracleSE1,
37+
instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small),
38+
masterUsername: 'syscdk',
39+
vpc
40+
});
41+
```
42+
By default, the master password will be generated and stored in AWS Secrets Manager.
43+
44+
Use `DatabaseInstanceFromSnapshot` and `DatabaseInstanceReadReplica` to create an instance from snapshot or
45+
a source database respectively:
46+
47+
```ts
48+
new DatabaseInstanceFromSnapshot(stack, 'Instance', {
49+
snapshotIdentifier: 'my-snapshot',
50+
engine: rds.DatabaseInstanceEngine.Postgres,
51+
instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large),
52+
vpc
53+
});
54+
55+
new DatabaseInstanceReadReplica(stack, 'ReadReplica', {
56+
sourceDatabaseInstance: sourceInstance,
57+
engine: rds.DatabaseInstanceEngine.Postgres,
58+
instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large),
59+
vpc
60+
});
61+
```
62+
Creating a "production" Oracle database instance with option and parameter groups:
63+
64+
[example of setting up a production oracle instance](test/integ.instance.lit.ts)
65+
66+
67+
### Instance events
68+
To define Amazon CloudWatch event rules for database instances, use the `onEvent`
69+
method:
70+
71+
```ts
72+
const rule = instance.onEvent('InstanceEvent', { target: new targets.LambdaFunction(fn) });
73+
```
74+
4875
### Connecting
4976

50-
To control who can access the cluster, use the `.connections` attribute. RDS database have
77+
To control who can access the cluster or instance, use the `.connections` attribute. RDS databases have
5178
a default port, so you don't need to specify the port:
5279

5380
```ts
5481
cluster.connections.allowFromAnyIpv4('Open to the world');
5582
```
5683

57-
The endpoints to access your database will be available as the `.clusterEndpoint` and `.readerEndpoint`
84+
The endpoints to access your database cluster will be available as the `.clusterEndpoint` and `.readerEndpoint`
5885
attributes:
5986

6087
```ts
6188
const writeAddress = cluster.clusterEndpoint.socketAddress; // "HOSTNAME:PORT"
6289
```
6390

91+
For an instance database:
92+
```ts
93+
const address = instance.instanceEndpoint.socketAddress; // "HOSTNAME:PORT"
94+
```
95+
6496
### Rotating master password
6597
When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically:
6698

67-
[example of setting up master password rotation](test/integ.cluster-rotation.lit.ts)
99+
[example of setting up master password rotation for a cluster](test/integ.cluster-rotation.lit.ts)
68100

69101
Rotation of the master password is also supported for an existing cluster:
70102
```ts
71-
new RotationSingleUser(stack, 'Rotation', {
103+
new SecretRotation(stack, 'Rotation', {
72104
secret: importedSecret,
73-
engine: DatabaseEngine.Oracle,
74-
target: importedCluster,
105+
application: SecretRotationApplication.OracleRotationSingleUser
106+
target: importedCluster, // or importedInstance
75107
vpc: importedVpc,
76108
})
77109
```
@@ -87,3 +119,13 @@ The `importedSecret` must be a JSON string with the following format:
87119
"port": "<optional: if not specified, default port will be used>"
88120
}
89121
```
122+
123+
### Metrics
124+
Database instances expose metrics (`cloudwatch.Metric`):
125+
```ts
126+
// The number of database connections in use (average over 5 minutes)
127+
const dbConnections = instance.metricDatabaseConnections();
128+
129+
// The average amount of time taken per disk I/O operation (average over 1 minute)
130+
const readLatency = instance.metric('ReadLatency', { statistic: 'Average', periodSec: 60 });
131+
```

packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts

Lines changed: 0 additions & 107 deletions
This file was deleted.

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

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import ec2 = require('@aws-cdk/aws-ec2');
22
import secretsmanager = require('@aws-cdk/aws-secretsmanager');
3-
import cdk = require('@aws-cdk/cdk');
4-
import { Token } from '@aws-cdk/cdk';
3+
import { IResource } from '@aws-cdk/cdk';
4+
import { Endpoint } from './endpoint';
55

66
/**
77
* Create a clustered database with a given number of instances.
88
*/
9-
export interface IDatabaseCluster extends cdk.IResource, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget {
9+
export interface IDatabaseCluster extends IResource, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget {
1010
/**
1111
* Identifier of the cluster
1212
*/
@@ -80,33 +80,3 @@ export interface DatabaseClusterAttributes {
8080
*/
8181
readonly instanceEndpointAddresses: string[];
8282
}
83-
84-
/**
85-
* Connection endpoint of a database cluster or instance
86-
*
87-
* Consists of a combination of hostname and port.
88-
*/
89-
export class Endpoint {
90-
/**
91-
* The hostname of the endpoint
92-
*/
93-
public readonly hostname: string;
94-
95-
/**
96-
* The port of the endpoint
97-
*/
98-
public readonly port: number;
99-
100-
/**
101-
* The combination of "HOSTNAME:PORT" for this endpoint
102-
*/
103-
public readonly socketAddress: string;
104-
105-
constructor(address: string, port: number) {
106-
this.hostname = address;
107-
this.port = port;
108-
109-
const portDesc = Token.isToken(port) ? '{IndirectPort}' : port;
110-
this.socketAddress = `${address}:${portDesc}`;
111-
}
112-
}

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

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import ec2 = require('@aws-cdk/aws-ec2');
22
import kms = require('@aws-cdk/aws-kms');
33
import secretsmanager = require('@aws-cdk/aws-secretsmanager');
44
import { Construct, DeletionPolicy, Resource, Token } from '@aws-cdk/cdk';
5-
import { IClusterParameterGroup } from './cluster-parameter-group';
6-
import { DatabaseClusterAttributes, Endpoint, IDatabaseCluster } from './cluster-ref';
5+
import { DatabaseClusterAttributes, IDatabaseCluster } from './cluster-ref';
76
import { DatabaseSecret } from './database-secret';
7+
import { Endpoint } from './endpoint';
8+
import { IParameterGroup } from './parameter-group';
89
import { BackupProps, DatabaseClusterEngine, InstanceProps, Login } from './props';
910
import { CfnDBCluster, CfnDBInstance, CfnDBSubnetGroup } from './rds.generated';
10-
import { DatabaseEngine, RotationSingleUser, RotationSingleUserOptions } from './rotation-single-user';
11+
import { SecretRotation, SecretRotationApplication, SecretRotationOptions } from './secret-rotation';
1112

1213
/**
1314
* Properties for a new database cluster
@@ -111,7 +112,7 @@ export interface DatabaseClusterProps {
111112
*
112113
* @default - No parameter group.
113114
*/
114-
readonly parameterGroup?: IClusterParameterGroup;
115+
readonly parameterGroup?: IParameterGroup;
115116

116117
/**
117118
* The CloudFormation policy to apply when the cluster and its instances
@@ -241,7 +242,7 @@ export class DatabaseCluster extends DatabaseClusterBase {
241242
/**
242243
* The database engine of this cluster
243244
*/
244-
public readonly engine: DatabaseClusterEngine;
245+
private readonly secretRotationApplication: SecretRotationApplication;
245246

246247
/**
247248
* The VPC where the DB subnet group is created.
@@ -286,11 +287,11 @@ export class DatabaseCluster extends DatabaseClusterBase {
286287
});
287288
}
288289

289-
this.engine = props.engine;
290+
this.secretRotationApplication = props.engine.secretRotationApplication;
290291

291292
const cluster = new CfnDBCluster(this, 'Resource', {
292293
// Basic
293-
engine: this.engine,
294+
engine: props.engine.name,
294295
dbClusterIdentifier: props.clusterIdentifier,
295296
dbSubnetGroupName: subnetGroup.ref,
296297
vpcSecurityGroupIds: [this.securityGroupId],
@@ -347,14 +348,15 @@ export class DatabaseCluster extends DatabaseClusterBase {
347348

348349
const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, {
349350
// Link to cluster
350-
engine: props.engine,
351+
engine: props.engine.name,
351352
dbClusterIdentifier: cluster.ref,
352353
dbInstanceIdentifier: instanceIdentifier,
353354
// Instance properties
354355
dbInstanceClass: databaseInstanceType(props.instanceProps.instanceType),
355356
publiclyAccessible,
356357
// This is already set on the Cluster. Unclear to me whether it should be repeated or not. Better yes.
357358
dbSubnetGroupName: subnetGroup.ref,
359+
dbParameterGroupName: props.instanceProps.parameterGroup && props.instanceProps.parameterGroup.parameterGroupName,
358360
});
359361

360362
instance.options.deletionPolicy = deleteReplacePolicy;
@@ -375,13 +377,13 @@ export class DatabaseCluster extends DatabaseClusterBase {
375377
/**
376378
* Adds the single user rotation of the master password to this cluster.
377379
*/
378-
public addRotationSingleUser(id: string, options: RotationSingleUserOptions = {}): RotationSingleUser {
380+
public addRotationSingleUser(id: string, options: SecretRotationOptions = {}): SecretRotation {
379381
if (!this.secret) {
380382
throw new Error('Cannot add single user rotation for a cluster without secret.');
381383
}
382-
return new RotationSingleUser(this, id, {
384+
return new SecretRotation(this, id, {
383385
secret: this.secret,
384-
engine: toDatabaseEngine(this.engine),
386+
application: this.secretRotationApplication,
385387
vpc: this.vpc,
386388
vpcSubnets: this.vpcSubnets,
387389
target: this,
@@ -396,20 +398,3 @@ export class DatabaseCluster extends DatabaseClusterBase {
396398
function databaseInstanceType(instanceType: ec2.InstanceType) {
397399
return 'db.' + instanceType.toString();
398400
}
399-
400-
/**
401-
* Transforms a DatbaseClusterEngine to a DatabaseEngine.
402-
*
403-
* @param engine the engine to transform
404-
*/
405-
function toDatabaseEngine(engine: DatabaseClusterEngine): DatabaseEngine {
406-
switch (engine) {
407-
case DatabaseClusterEngine.Aurora:
408-
case DatabaseClusterEngine.AuroraMysql:
409-
return DatabaseEngine.Mysql;
410-
case DatabaseClusterEngine.AuroraPostgresql:
411-
return DatabaseEngine.Postgres;
412-
default:
413-
throw new Error('Unknown engine');
414-
}
415-
}

0 commit comments

Comments
 (0)