Skip to content

Commit

Permalink
fix(rds): Make most DatabaseClusterAttributes properties optional
Browse files Browse the repository at this point in the history
The `IDatabaseCluster` interface has a lot of properties, putting a large strain
on users who want to import clusters via `DatabaseClusterAttributes`. This
alters the attributes interface to contain almost exclusively optional props,
and lazily throw if any missing properties are accessed.

I believe this is a backwards-compatible change, but someone please double-check
me on that.

fixes #3587
  • Loading branch information
njlynch committed Sep 10, 2020
1 parent 238d963 commit 2b3e14c
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 24 deletions.
32 changes: 22 additions & 10 deletions packages/@aws-cdk/aws-rds/lib/cluster-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,38 +46,50 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable, secretsma
*/
export interface DatabaseClusterAttributes {
/**
* The database port
* Identifier for the cluster
*/
readonly port: number;
readonly clusterIdentifier: string;

/**
* The security groups of the database cluster
* The database port
*
* @default - none
*/
readonly securityGroups: ec2.ISecurityGroup[];
readonly port?: number;

/**
* Identifier for the cluster
* The security groups of the database cluster
*
* @default - no security groups
*/
readonly clusterIdentifier: string;
readonly securityGroups?: ec2.ISecurityGroup[];

/**
* Identifier for the instances
*
* @default - no instance identifiers
*/
readonly instanceIdentifiers: string[];
readonly instanceIdentifiers?: string[];
// Actual underlying type: DBInstanceId[], but we have to type it more loosely for Java's benefit.

/**
* Cluster endpoint address
*
* @default - no endpoint address
*/
readonly clusterEndpointAddress: string;
readonly clusterEndpointAddress?: string;

/**
* Reader endpoint address
*
* @default - no reader address
*/
readonly readerEndpointAddress: string;
readonly readerEndpointAddress?: string;

/**
* Endpoint addresses of individual instances
*
* @default - no instance endpoints
*/
readonly instanceEndpointAddresses: string[];
readonly instanceEndpointAddresses?: string[];
}
75 changes: 61 additions & 14 deletions packages/@aws-cdk/aws-rds/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,66 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase {
}
}

/**
* Represents an imported database cluster.
*/
class ImportedDatabaseCluster extends DatabaseClusterBase implements IDatabaseCluster {
public readonly clusterIdentifier: string;
public readonly connections: ec2.Connections;

private readonly _clusterEndpoint?: Endpoint;
private readonly _clusterReadEndpoint?: Endpoint;
private readonly _instanceIdentifiers?: string[];
private readonly _instanceEndpoints?: Endpoint[];

constructor(scope: Construct, id: string, attrs: DatabaseClusterAttributes) {
super(scope, id);

this.clusterIdentifier = attrs.clusterIdentifier;

const defaultPort = attrs.port ? ec2.Port.tcp(attrs.port) : undefined;
this.connections = new ec2.Connections({
securityGroups: attrs.securityGroups,
defaultPort,
});

this._clusterEndpoint = (attrs.clusterEndpointAddress && attrs.port) ? new Endpoint(attrs.clusterEndpointAddress, attrs.port) : undefined;
this._clusterReadEndpoint = (attrs.readerEndpointAddress && attrs.port) ? new Endpoint(attrs.readerEndpointAddress, attrs.port) : undefined;
this._instanceIdentifiers = attrs.instanceIdentifiers;
this._instanceEndpoints = (attrs.instanceEndpointAddresses && attrs.port)
? attrs.instanceEndpointAddresses.map(addr => new Endpoint(addr, attrs.port!))
: undefined;
}

public get clusterEndpoint() {
if (!this._clusterEndpoint) {
throw new Error('Cannot access `clusterEndpoint` of an imported cluster without an endpoint address and port');
}
return this._clusterEndpoint;
}

public get clusterReadEndpoint() {
if (!this._clusterReadEndpoint) {
throw new Error('Cannot access `clusterReadEndpoint` of an imported cluster without a readerEndpointAddress and port');
}
return this._clusterReadEndpoint;
}

public get instanceIdentifiers() {
if (!this._instanceIdentifiers) {
throw new Error('Cannot access `instanceIdentifiers` of an imported cluster without provided instanceIdentifiers');
}
return this._instanceIdentifiers;
}

public get instanceEndpoints() {
if (!this._instanceEndpoints) {
throw new Error('Cannot access `instanceEndpoints` of an imported cluster without instanceEndpointAddresses and port');
}
return this._instanceEndpoints;
}
}

/**
* Properties for a new database cluster
*/
Expand Down Expand Up @@ -424,20 +484,7 @@ export class DatabaseCluster extends DatabaseClusterNew {
* Import an existing DatabaseCluster from properties
*/
public static fromDatabaseClusterAttributes(scope: Construct, id: string, attrs: DatabaseClusterAttributes): IDatabaseCluster {
class Import extends DatabaseClusterBase implements IDatabaseCluster {
public readonly defaultPort = ec2.Port.tcp(attrs.port);
public readonly connections = new ec2.Connections({
securityGroups: attrs.securityGroups,
defaultPort: this.defaultPort,
});
public readonly clusterIdentifier = attrs.clusterIdentifier;
public readonly instanceIdentifiers: string[] = [];
public readonly clusterEndpoint = new Endpoint(attrs.clusterEndpointAddress, attrs.port);
public readonly clusterReadEndpoint = new Endpoint(attrs.readerEndpointAddress, attrs.port);
public readonly instanceEndpoints = attrs.instanceEndpointAddresses.map(a => new Endpoint(a, attrs.port));
}

return new Import(scope, id);
return new ImportedDatabaseCluster(scope, id, attrs);
}

public readonly clusterIdentifier: string;
Expand Down
50 changes: 50 additions & 0 deletions packages/@aws-cdk/aws-rds/test/test.cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,56 @@ export = {
test.done();
},

'can import a cluster with minimal attributes'(test: Test) {
const stack = testStack();

const cluster = DatabaseCluster.fromDatabaseClusterAttributes(stack, 'Database', {
clusterIdentifier: 'identifier',
});

test.equals(cluster.clusterIdentifier, 'identifier');

test.done();
},

'minimal imported cluster throws on accessing attributes for unprovided parameters'(test: Test) {
const stack = testStack();

const cluster = DatabaseCluster.fromDatabaseClusterAttributes(stack, 'Database', {
clusterIdentifier: 'identifier',
});

test.throws(() => cluster.clusterEndpoint, /Cannot access `clusterEndpoint` of an imported cluster/);
test.throws(() => cluster.clusterReadEndpoint, /Cannot access `clusterReadEndpoint` of an imported cluster/);
test.throws(() => cluster.instanceIdentifiers, /Cannot access `instanceIdentifiers` of an imported cluster/);
test.throws(() => cluster.instanceEndpoints, /Cannot access `instanceEndpoints` of an imported cluster/);

test.done();
},

'imported cluster can access properties if attributes are provided'(test: Test) {
const stack = testStack();

const cluster = DatabaseCluster.fromDatabaseClusterAttributes(stack, 'Database', {
clusterEndpointAddress: 'addr',
clusterIdentifier: 'identifier',
instanceEndpointAddresses: ['instance-addr'],
instanceIdentifiers: ['identifier'],
port: 3306,
readerEndpointAddress: 'reader-address',
securityGroups: [ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789', {
allowAllOutbound: false,
})],
});

test.equals(cluster.clusterEndpoint.socketAddress, 'addr:3306');
test.equals(cluster.clusterReadEndpoint.socketAddress, 'reader-address:3306');
test.deepEqual(cluster.instanceIdentifiers, ['identifier']);
test.deepEqual(cluster.instanceEndpoints.map(endpoint => endpoint.socketAddress), ['instance-addr:3306']);

test.done();
},

'cluster supports metrics'(test: Test) {
const stack = testStack();
const vpc = new ec2.Vpc(stack, 'VPC');
Expand Down

0 comments on commit 2b3e14c

Please sign in to comment.