From 2541508320e091e5b744a640a4c8f29de77a23c9 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 17 Sep 2018 14:17:03 +0200 Subject: [PATCH] feat(aws-rds): add support for parameter groups (#729) Now supports (Cluster) Parameter Groups. This is required to support Aurora 5.7 engines (in particular, PostgreSQL with Aurora). Fixes #719. --- packages/@aws-cdk/aws-neptune/lib/index.ts | 9 ++- .../lib/cluster-parameter-group-ref.ts | 48 ++++++++++++ .../aws-rds/lib/cluster-parameter-group.ts | 77 +++++++++++++++++++ packages/@aws-cdk/aws-rds/lib/cluster.ts | 8 +- packages/@aws-cdk/aws-rds/lib/index.ts | 2 + .../aws-rds/test/integ.cluster.expected.json | 13 ++++ .../@aws-cdk/aws-rds/test/integ.cluster.ts | 10 ++- .../@aws-cdk/aws-rds/test/test.cluster.ts | 36 ++++++++- 8 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 packages/@aws-cdk/aws-rds/lib/cluster-parameter-group-ref.ts create mode 100644 packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts diff --git a/packages/@aws-cdk/aws-neptune/lib/index.ts b/packages/@aws-cdk/aws-neptune/lib/index.ts index 22cf821079fa3..f57e979144a7e 100644 --- a/packages/@aws-cdk/aws-neptune/lib/index.ts +++ b/packages/@aws-cdk/aws-neptune/lib/index.ts @@ -74,7 +74,12 @@ export interface NeptuneDatabaseProps { */ preferredMaintenanceWindow?: string; - // Additional parameters to the database engine go here + /** + * Parameter group with Neptune settings + * + * @default No parameter group + */ + parameterGroup?: rds.ClusterParameterGroupRef; } /** @@ -125,8 +130,8 @@ export class NeptuneDatabase extends cdk.Construct implements ec2.IConnectable { instanceIdentifierBase: props.instanceIdentifierBase, defaultDatabaseName: props.defaultDatabaseName, kmsKeyArn: props.kmsKeyArn, - parameters: {}, // Additional parameters go here preferredMaintenanceWindow: props.preferredMaintenanceWindow, + parameterGroup: props.parameterGroup, }); this.clusterIdentifier = this.cluster.clusterIdentifier; diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group-ref.ts b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group-ref.ts new file mode 100644 index 0000000000000..342a826649eb7 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group-ref.ts @@ -0,0 +1,48 @@ +import cdk = require('@aws-cdk/cdk'); +import { DBClusterParameterGroupName } from './rds.generated'; + +/** + * A cluster parameter group + */ +export abstract class ClusterParameterGroupRef extends cdk.Construct { + /** + * Import a parameter group + */ + public static import(parent: cdk.Construct, id: string, props: ClusterParameterGroupRefProps): ClusterParameterGroupRef { + return new ImportedClusterParameterGroup(parent, id, props); + } + + /** + * Name of this parameter group + */ + public abstract readonly parameterGroupName: DBClusterParameterGroupName; + + /** + * Export this parameter group + */ + public export(): ClusterParameterGroupRefProps { + return { + parameterGroupName: new DBClusterParameterGroupName( + new cdk.Output(this, 'ParameterGroupName', { value: this.parameterGroupName }).makeImportValue()) + }; + } +} + +/** + * Properties to reference a cluster parameter group + */ +export interface ClusterParameterGroupRefProps { + parameterGroupName: DBClusterParameterGroupName; +} + +/** + * An imported cluster parameter group + */ +class ImportedClusterParameterGroup extends ClusterParameterGroupRef { + public readonly parameterGroupName: DBClusterParameterGroupName; + + constructor(parent: cdk.Construct, id: string, props: ClusterParameterGroupRefProps) { + super(parent, id); + this.parameterGroupName = props.parameterGroupName; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts new file mode 100644 index 0000000000000..4fb81a8106c12 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts @@ -0,0 +1,77 @@ +import cdk = require('@aws-cdk/cdk'); +import { ClusterParameterGroupRef } from './cluster-parameter-group-ref'; +import { Parameters } from './props'; +import { cloudformation, DBClusterParameterGroupName } from './rds.generated'; + +/** + * Properties for a cluster parameter group + */ +export interface ClusterParameterGroupProps { + /** + * Database family of this parameter group + */ + family: string; + + /** + * Description for this parameter group + */ + description: string; + + /** + * The parameters in this parameter group + */ + parameters?: Parameters; +} + +/** + * Defina a cluster parameter group + */ +export class ClusterParameterGroup extends ClusterParameterGroupRef { + public readonly parameterGroupName: DBClusterParameterGroupName; + private readonly parameters: Parameters = {}; + + constructor(parent: cdk.Construct, id: string, props: ClusterParameterGroupProps) { + super(parent, id); + + const resource = new cloudformation.DBClusterParameterGroupResource(this, 'Resource', { + description: props.description, + family: props.family, + parameters: new cdk.Token(() => this.parameters), + }); + + for (const [key, value] of Object.entries(props.parameters || {})) { + this.setParameter(key, value); + } + + this.parameterGroupName = resource.ref; + } + + /** + * Set a single parameter in this parameter group + */ + public setParameter(key: string, value: string | undefined) { + if (value === undefined && key in this.parameters) { + delete this.parameters[key]; + } + if (value !== undefined) { + this.parameters[key] = value; + } + } + + /** + * Remove a previously-set parameter from this parameter group + */ + public removeParameter(key: string) { + this.setParameter(key, undefined); + } + + /** + * Validate this construct + */ + public validate(): string[] { + if (Object.keys(this.parameters).length === 0) { + return ['At least one parameter required, call setParameter().']; + } + return []; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 8dbd844ca1432..1b321bdfb3f2a 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -1,8 +1,9 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); import cdk = require('@aws-cdk/cdk'); +import { ClusterParameterGroupRef } from './cluster-parameter-group-ref'; import { DatabaseClusterRef, Endpoint } from './cluster-ref'; -import { BackupProps, DatabaseClusterEngine, InstanceProps, Login, Parameters } from './props'; +import { BackupProps, DatabaseClusterEngine, InstanceProps, Login } from './props'; import { cloudformation, DBClusterEndpointAddress, DBClusterEndpointPort, DBClusterName, DBInstanceId } from './rds.generated'; /** @@ -84,8 +85,10 @@ export interface DatabaseClusterProps { /** * Additional parameters to pass to the database engine + * + * @default No parameter group */ - parameters?: Parameters; + parameterGroup?: ClusterParameterGroupRef; } /** @@ -155,6 +158,7 @@ export class DatabaseCluster extends DatabaseClusterRef { dbSubnetGroupName: subnetGroup.ref, vpcSecurityGroupIds: [this.securityGroupId], port: props.port, + dbClusterParameterGroupName: props.parameterGroup && props.parameterGroup.parameterGroupName, // Admin masterUsername: props.masterUser.username, masterUserPassword: props.masterUser.password, diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index f48d1b86f2909..abd9739d1f8f1 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -2,6 +2,8 @@ export * from './cluster'; export * from './cluster-ref'; export * from './instance'; export * from './props'; +export * from './cluster-parameter-group'; +export * from './cluster-parameter-group-ref'; // AWS::RDS CloudFormation Resources: export * from './rds.generated'; diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json index d7c0ecd373561..3a3c1b4ae947f 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json @@ -305,6 +305,16 @@ } } }, + "ParamsA8366201": { + "Type": "AWS::RDS::DBClusterParameterGroup", + "Properties": { + "Description": "A nice parameter group", + "Family": "aurora5.6", + "Parameters": { + "character_set_database": "utf8mb4" + } + } + }, "DatabaseSubnets56F17B9A": { "Type": "AWS::RDS::DBSubnetGroup", "Properties": { @@ -360,6 +370,9 @@ "Type": "AWS::RDS::DBCluster", "Properties": { "Engine": "aurora", + "DBClusterParameterGroupName": { + "Ref": "ParamsA8366201" + }, "DBSubnetGroupName": { "Ref": "DatabaseSubnets56F17B9A" }, diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index 2458f833319fb..adf5cdfd88458 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -1,12 +1,19 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { DatabaseCluster, DatabaseClusterEngine, Password, Username } from '../lib'; +import { ClusterParameterGroup } from '../lib/cluster-parameter-group'; const app = new cdk.App(process.argv); const stack = new cdk.Stack(app, 'aws-cdk-rds-integ'); const vpc = new ec2.VpcNetwork(stack, 'VPC', { maxAZs: 2 }); +const params = new ClusterParameterGroup(stack, 'Params', { + family: 'aurora5.6', + description: 'A nice parameter group', +}); +params.setParameter('character_set_database', 'utf8mb4'); + const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.Aurora, masterUser: { @@ -17,7 +24,8 @@ const cluster = new DatabaseCluster(stack, 'Database', { instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), vpcPlacement: { subnetsToUse: ec2.SubnetType.Public }, vpc - } + }, + parameterGroup: params, }); cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world'); diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index c753d8a848606..0f23a5395227a 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -2,7 +2,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; -import { DatabaseCluster, DatabaseClusterEngine, DatabaseClusterRef, Password, Username } from '../lib'; +import { ClusterParameterGroup, DatabaseCluster, DatabaseClusterEngine, DatabaseClusterRef, Password, Username } from '../lib'; export = { 'check that instantiation works'(test: Test) { @@ -88,6 +88,40 @@ export = { test.done(); }, + + 'cluster with parameter group'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // WHEN + const group = new ClusterParameterGroup(stack, 'Params', { + family: 'hello', + description: 'bye', + parameters: { + param: 'value' + } + }); + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.Aurora, + masterUser: { + username: new Username('admin'), + password: new Password('tooshort'), + }, + instanceProps: { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + vpc + }, + parameterGroup: group + }); + + // THEN + expect(stack).to(haveResource('AWS::RDS::DBCluster', { + DBClusterParameterGroupName: { Ref: 'ParamsA8366201' }, + })); + + test.done(); + }, }; function testStack() {