From d58f8ef646030f5a3081a248673cc853682a4dcb Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Tue, 2 Jun 2020 17:51:38 -0700 Subject: [PATCH] feat(rds): change the way Engines are modeled Change the types of the engine from DatabaseClusterEngine and its subclass, DatabaseInstanceEngine, to 2 separate interfaces, IClusterEngine and IInstanceEngine. Add properties to each of them, including engineVersion, and thus stop taking engineVersion separately as a property for instances, clusters and OptionGroups. Allow changing the version of an existing engine to an arbitrary string. Add a bind()-like protocol to both IClusterEngine and IInstanceEngine, which allows expressing validation and default values logic directly in the engines, instead of hard-coding them in cluster and instance. Because of those changes, creating a default cluster with Aurora MySQL or Postgres engines now works out of the box, instead of failing at deploy time. We also correctly set the port for Postgres clusters to 5432, instead of leaving it as the default 3306 (which is the MySQL port). Fixes #2213 Fixes #2512 Fixes #4150 Fixes #5126 Fixes #7072 BREAKING CHANGE: the class DatabaseClusterEngine has been replaced with the interface IClusterEngine in the type of DatabaseClusterProps.engine * **rds**: the class DatabaseInstanceEngine has been replaced with the interface IInstanceEngine in the type of DatabaseInstanceSourceProps.engine * **rds**: DatabaseClusterProps.engineVersion has been removed; instead, create an IClusterEngine with a specific version using the static factory methods in DatabaseClusterEngine * **rds**: DatabaseInstanceSourceProps.engineVersion has been removed; instead, create an IInstanceEngine with a specific version using the static factory methods in DatabaseInstanceEngine * **rds**: the property majorEngineVersion can no longer be passed when creating an OptionGroup; instead, create an IInstanceEngine with a specific version using the static factory methods in DatabaseInstanceEngine --- packages/@aws-cdk/aws-rds/README.md | 122 ++-- .../@aws-cdk/aws-rds/lib/cluster-engine.ts | 318 +++++++++++ packages/@aws-cdk/aws-rds/lib/cluster.ts | 59 +- packages/@aws-cdk/aws-rds/lib/index.ts | 2 + .../@aws-cdk/aws-rds/lib/instance-engine.ts | 526 ++++++++++++++++++ packages/@aws-cdk/aws-rds/lib/instance.ts | 130 +---- packages/@aws-cdk/aws-rds/lib/option-group.ts | 21 +- .../@aws-cdk/aws-rds/lib/parameter-group.ts | 35 +- .../private/parameter-group-family-mapping.ts | 14 + packages/@aws-cdk/aws-rds/lib/props.ts | 92 +-- .../aws-rds/test/integ.instance.lit.ts | 5 +- .../aws-rds/test/test.cluster-engine.ts | 113 ++++ .../@aws-cdk/aws-rds/test/test.cluster.ts | 44 +- .../@aws-cdk/aws-rds/test/test.instance.ts | 9 +- .../aws-rds/test/test.option-group.ts | 19 +- packages/@aws-cdk/aws-rds/test/test.props.ts | 111 ---- 16 files changed, 1196 insertions(+), 424 deletions(-) create mode 100644 packages/@aws-cdk/aws-rds/lib/cluster-engine.ts create mode 100644 packages/@aws-cdk/aws-rds/lib/instance-engine.ts create mode 100644 packages/@aws-cdk/aws-rds/lib/private/parameter-group-family-mapping.ts create mode 100644 packages/@aws-cdk/aws-rds/test/test.cluster-engine.ts delete mode 100644 packages/@aws-cdk/aws-rds/test/test.props.ts diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 64d7b7b19528b..0bbd41e66a7c0 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -1,4 +1,5 @@ ## Amazon Relational Database Service Construct Library + --- @@ -13,59 +14,97 @@ --- -### Starting a Clustered Database +```typescript +import * as rds from '@aws-cdk/aws-rds'; +``` + +### Starting a clustered database To set up a clustered database (like Aurora), define a `DatabaseCluster`. You must always launch a database in a VPC. Use the `vpcSubnets` attribute to control whether your instances will be launched privately or publicly: ```ts -const cluster = new DatabaseCluster(this, 'Database', { - engine: DatabaseClusterEngine.AURORA, - masterUser: { - username: 'clusteradmin' +const cluster = new rds.DatabaseCluster(this, 'Database', { + engine: rds.DatabaseClusterEngine.AURORA, + masterUser: { + username: 'clusteradmin' + }, + instanceProps: { + // optional, defaults to the engine's default instance type + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE, }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, - }, - vpc - } + vpc, + }, }); ``` + +To use a specific version of the engine +(which is recommended, in order to avoid surprise updates when RDS add support for a newer version of the engine), +use the static factory methods on `DatabaseClusterEngine`: + +```typescript +new rds.DatabaseCluster(this, 'Database', { + engine: rds.DatabaseClusterEngine.defaultAurora({ + engineVersion: '5.6.mysql_aurora.1.17.9', + }, + ... +}) +``` + +See [the AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#cfn-rds-dbinstance-engineversion) +for a list of the supported versions for each engine type. + By default, the master password will be generated and stored in AWS Secrets Manager with auto-generated description. Your cluster will be empty by default. To add a default database upon construction, specify the `defaultDatabaseName` attribute. -### Starting an Instance Database +### Starting an instance database + To set up a instance database, define a `DatabaseInstance`. You must always launch a database in a VPC. Use the `vpcSubnets` attribute to control whether your instances will be launched privately or publicly: ```ts -const instance = new DatabaseInstance(stack, 'Instance', { - engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'syscdk', - vpc +const instance = new rds.DatabaseInstance(this, 'Instance', { + engine: rds.DatabaseInstanceEngine.ORACLE_SE1, + // optional, defaults to the engine's default instance type + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + masterUsername: 'syscdk', + vpc, }); ``` + By default, the master password will be generated and stored in AWS Secrets Manager. +You specify the engine version +(which is recommended, in order to avoid surprise updates when RDS add support for a newer version of the engine) +the same as with cluster, +by calling instance methods on the `engine`: + +```typescript +const instance = new rds.DatabaseInstance(this, 'Instance', { + engine: rds.DatabaseInstanceEngine.ORACLE_SE1.withVersion('19.0.0.0'), + ... +}); +``` + To use the storage auto scaling option of RDS you can specify the maximum allocated storage. This is the upper limit to which RDS can automatically scale the storage. More info can be found [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PIOPS.StorageTypes.html#USER_PIOPS.Autoscaling) Example for max storage configuration: ```ts -const instance = new DatabaseInstance(stack, 'Instance', { - engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'syscdk', - vpc, - maxAllocatedStorage: 200 +const instance = new rds.DatabaseInstance(this, 'Instance', { + engine: rds.DatabaseInstanceEngine.ORACLE_SE1, + // optional, defaults to the engine's default instance type + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + masterUsername: 'syscdk', + vpc, + maxAllocatedStorage: 200, }); ``` @@ -73,17 +112,18 @@ Use `DatabaseInstanceFromSnapshot` and `DatabaseInstanceReadReplica` to create a a source database respectively: ```ts -new DatabaseInstanceFromSnapshot(stack, 'Instance', { - snapshotIdentifier: 'my-snapshot', - engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), - vpc +new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.POSTGRES, + // optional, defaults to the engine's default instance type + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + vpc, }); -new DatabaseInstanceReadReplica(stack, 'ReadReplica', { - sourceDatabaseInstance: sourceInstance, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), - vpc +new rds.DatabaseInstanceReadReplica(stack, 'ReadReplica', { + sourceDatabaseInstance: sourceInstance, + instanceType: rds.DatabaseInstanceEngine.POSTGRES.defaultInstanceType, + vpc, }); ``` @@ -91,8 +131,8 @@ Creating a "production" Oracle database instance with option and parameter group [example of setting up a production oracle instance](test/integ.instance.lit.ts) - ### Instance events + To define Amazon CloudWatch event rules for database instances, use the `onEvent` method: @@ -122,7 +162,9 @@ const address = instance.instanceEndpoint.socketAddress; // "HOSTNAME:PORT" ``` ### Rotating credentials + When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically: + ```ts instance.addRotationSingleUser(); // Will rotate automatically after 30 days ``` @@ -154,7 +196,9 @@ The rotation will start as soon as this user exists. See also [@aws-cdk/aws-secretsmanager](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-secretsmanager/README.md) for credentials rotation of existing clusters/instances. ### Metrics + Database instances expose metrics (`cloudwatch.Metric`): + ```ts // The number of database connections in use (average over 5 minutes) const dbConnections = instance.metricDatabaseConnections(); @@ -181,11 +225,13 @@ data into S3](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/postg The following snippet sets up a database cluster with different S3 buckets where the data is imported and exported - ```ts +import * as s3 from '@aws-cdk/aws-s3'; + const importBucket = new s3.Bucket(this, 'importbucket'); const exportBucket = new s3.Bucket(this, 'exportbucket'); -new DatabaseCluster(this, 'dbcluster', { - // ... - s3ImportBuckets: [ importBucket ], - s3ExportBuckets: [ exportBucket ] +new rds.DatabaseCluster(this, 'dbcluster', { + // ... + s3ImportBuckets: [importBucket], + s3ExportBuckets: [exportBucket], }); ``` diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts new file mode 100644 index 0000000000000..bdab7bb30e4e1 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -0,0 +1,318 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; +import * as core from '@aws-cdk/core'; +import { ClusterParameterGroup, IParameterGroup, ParameterGroup } from './parameter-group'; +import { ParameterGroupFamilyMapping } from './private/parameter-group-family-mapping'; +import { compare } from './private/version'; + +/** + * The extra options passed to the {@link IClusterEngine.bindToCluster} method. + */ +export interface ClusterEngineBindOptions { + /** + * The role used for S3 importing. + * + * @default - none + */ + readonly s3ImportRole?: iam.IRole; + + /** + * The role used for S3 exporting. + * + * @default - none + */ + readonly s3ExportRole?: iam.IRole; + + /** + * The customer-provided ParameterGroup. + * + * @default - none + */ + readonly parameterGroup?: IParameterGroup; +} + +/** + * The type returned from the {@link IClusterEngine.bindToCluster} method. + */ +export interface ClusterEngineConfig { + /** + * The ParameterGroup to use for the cluster. + * + * @default - no ParameterGroup will be used + */ + readonly parameterGroup?: IParameterGroup; + + /** + * The port to use for this cluster, + * unless the customer specified the port directly. + * + * @default - use the default port for clusters (3306) + */ + readonly port?: number; +} + +/** + * The interface representing a database cluster (as opposed to instance) engine. + */ +export interface IClusterEngine { + /** The type of the engine, for example "aurora-mysql". */ + readonly engineType: string; + + /** + * The exact version of a given engine. + * + * @default - default version for the given engine type + */ + readonly engineVersion?: string; + + /** + * The family to use for ParameterGroups using this engine. + * This is usually equal to "", + * but can sometimes be a variation of that. + * You can pass this property when creating new ClusterParameterGroup. + */ + readonly parameterGroupFamily: string; + + /** The application used by this engine to perform rotation for a single-user scenario. */ + readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + + /** The application used by this engine to perform rotation for a multi-user scenario. */ + readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + + /** The default EC2 instance type to use for databases in this cluster. */ + readonly defaultInstanceType: ec2.InstanceType; + + /** + * Method called when the engine is used to create a new cluster. + */ + bindToCluster(scope: core.Construct, options: ClusterEngineBindOptions): ClusterEngineConfig; +} + +interface BaseClusterEngineProps { + readonly engineType: string; + readonly needsS3RolesInParameters: boolean; + readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + readonly parameterGroupFamilies?: ParameterGroupFamilyMapping[]; + readonly defaultPort?: number; + readonly engineVersion?: string; +} + +abstract class BaseClusterEngine implements IClusterEngine { + public readonly engineType: string; + public readonly engineVersion?: string; + public readonly parameterGroupFamily: string; + public readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + public readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + // this works for all 3 cluster engines that are currently supported + public readonly defaultInstanceType = ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM); + + private readonly needsS3RolesInParameters: boolean; + private readonly parameterGroupFamilies?: ParameterGroupFamilyMapping[]; + private readonly defaultPort?: number; + + constructor(props: BaseClusterEngineProps) { + this.engineType = props.engineType; + this.needsS3RolesInParameters = props.needsS3RolesInParameters; + this.singleUserRotationApplication = props.singleUserRotationApplication; + this.multiUserRotationApplication = props.multiUserRotationApplication; + this.parameterGroupFamilies = props.parameterGroupFamilies; + this.defaultPort = props.defaultPort; + this.engineVersion = props.engineVersion; + this.parameterGroupFamily = this.establishParameterGroupFamily(); + } + + public bindToCluster(scope: core.Construct, options: ClusterEngineBindOptions): ClusterEngineConfig { + let parameterGroup: IParameterGroup | undefined; + if (options.parameterGroup) { + parameterGroup = options.parameterGroup; + } else if (this.needsS3RolesInParameters && (options.s3ImportRole || options.s3ExportRole)) { + // in this case, we need to create a new ParameterGroup to store the S3 Role parameters in + // (imported ParameterGroups, like the RDS default ones, don't allow adding parameters to them) + parameterGroup = new ClusterParameterGroup(scope, 'ClusterParameterGroup', { + family: this.parameterGroupFamily, + }); + } else { + parameterGroup = this.defaultParameterGroup(scope); + } + + if (this.needsS3RolesInParameters) { + if (options.s3ImportRole) { + parameterGroup?.addParameter('aurora_load_from_s3_role', options.s3ImportRole.roleArn); + } + if (options.s3ExportRole) { + parameterGroup?.addParameter('aurora_select_into_s3_role', options.s3ExportRole.roleArn); + } + } + + return { + parameterGroup, + port: this.defaultPort, + }; + } + + protected abstract defaultParameterGroup(scope: core.Construct): IParameterGroup | undefined; + + private establishParameterGroupFamily(): string { + const ret = this.calculateParameterGroupFamily(); + if (ret === undefined) { + throw new Error(`No parameter group family found for database engine ${this.engineType} with version ${this.engineVersion}.`); + } + return ret; + } + + /** + * Get the latest parameter group family for this engine. Latest is determined using semver on the engine major version. + * When `engineVersion` is specified, return the parameter group family corresponding to that engine version. + * Return undefined if no parameter group family is defined for this engine or for the requested `engineVersion`. + */ + private calculateParameterGroupFamily(): string | undefined { + if (this.parameterGroupFamilies === undefined) { + return undefined; + } + const engineVersion = this.engineVersion; + if (engineVersion !== undefined) { + const family = this.parameterGroupFamilies.find(x => engineVersion.startsWith(x.engineMajorVersion)); + if (family) { + return family.parameterGroupFamily; + } + } else if (this.parameterGroupFamilies.length > 0) { + const sorted = this.parameterGroupFamilies.slice().sort((a, b) => { + return compare(a.engineMajorVersion, b.engineMajorVersion); + }).reverse(); + return sorted[0].parameterGroupFamily; + } + return undefined; + } +} + +/** + * Creation properties of the plain Aurora database cluster engine. + * Used in {@link DatabaseClusterEngine.defaultAurora}. + */ +export interface DefaultAuroraClusterEngineProps { + /** + * The exact version of the engine. + * + * @example "5.6.mysql_aurora.1.22.2" + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class DefaultAuroraClusterEngine extends BaseClusterEngine { + constructor(props: DefaultAuroraClusterEngineProps = {}) { + super({ + engineType: 'aurora', + needsS3RolesInParameters: true, + singleUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, + parameterGroupFamilies: [ + { engineMajorVersion: '5.6', parameterGroupFamily: 'aurora5.6' }, + ], + engineVersion: props.engineVersion, + }); + } + + protected defaultParameterGroup(_scope: core.Construct): IParameterGroup | undefined { + // the default.aurora5.6 ParameterGroup is actually the default, + // so just return undefined in this case + return undefined; + } +} + +abstract class NonDefaultAuroraClusterEngine extends BaseClusterEngine { + protected defaultParameterGroup(scope: core.Construct): IParameterGroup | undefined { + return ParameterGroup.fromParameterGroupName(scope, 'DatabaseClusterEngineDefaultParameterGroup', + `default.${this.parameterGroupFamily}`); + } +} + +/** + * Creation properties of the Aurora MySQL database cluster engine. + * Used in {@link DatabaseClusterEngine.auroraMySql}. + */ +export interface AuroraMySqlClusterEngineProps { + /** + * The exact version of the engine. + * + * @example "5.7.mysql_aurora.2.07.1" + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class AuroraMySqlClusterEngine extends NonDefaultAuroraClusterEngine { + constructor(props: AuroraMySqlClusterEngineProps = {}) { + super({ + engineType: 'aurora-mysql', + needsS3RolesInParameters: true, + singleUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, + parameterGroupFamilies: [ + { engineMajorVersion: '5.7', parameterGroupFamily: 'aurora-mysql5.7' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +/** + * Creation properties of the Aurora MySQL database cluster engine. + * Used in {@link DatabaseClusterEngine.auroraMySql}. + */ +export interface AuroraPostgresClusterEngineProps { + /** + * The exact version of the engine. + * + * @example "9.5.21" + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class AuroraPostgresClusterEngine extends NonDefaultAuroraClusterEngine { + constructor(props: AuroraMySqlClusterEngineProps = {}) { + super({ + engineType: 'aurora-postgresql', + needsS3RolesInParameters: false, + singleUserRotationApplication: secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_MULTI_USER, + parameterGroupFamilies: [ + { engineMajorVersion: '9.6', parameterGroupFamily: 'aurora-postgresql9.6' }, + { engineMajorVersion: '10', parameterGroupFamily: 'aurora-postgresql10' }, + { engineMajorVersion: '11', parameterGroupFamily: 'aurora-postgresql11' }, + ], + defaultPort: 5432, + engineVersion: props.engineVersion, + }); + } +} + +/** + * A database cluster engine. Provides mapping to the serverless application + * used for secret rotation. + */ +export class DatabaseClusterEngine { + public static readonly AURORA: IClusterEngine = new DefaultAuroraClusterEngine(); + + public static readonly AURORA_MYSQL: IClusterEngine = new AuroraMySqlClusterEngine(); + + public static readonly AURORA_POSTGRESQL: IClusterEngine = new AuroraPostgresClusterEngine(); + + /** Creates a new plain Aurora database cluster engine. */ + public static defaultAurora(props: DefaultAuroraClusterEngineProps): IClusterEngine { + return new DefaultAuroraClusterEngine(props); + } + + /** Creates a new Aurora MySQL database cluster engine. */ + public static auroraMySql(props: AuroraMySqlClusterEngineProps): IClusterEngine { + return new AuroraMySqlClusterEngine(props); + } + + /** Creates a new Aurora Postgres database cluster engine. */ + public static auroraPostgres(props: AuroraPostgresClusterEngineProps): IClusterEngine { + return new AuroraPostgresClusterEngine(props); + } +} diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index b11bdb40a84ff..cec8d8ed70dfc 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -4,11 +4,12 @@ import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { CfnDeletionPolicy, Construct, Duration, RemovalPolicy, Resource, Token } from '@aws-cdk/core'; +import { IClusterEngine } from './cluster-engine'; import { DatabaseClusterAttributes, IDatabaseCluster } from './cluster-ref'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; -import { ClusterParameterGroup, IParameterGroup } from './parameter-group'; -import { BackupProps, DatabaseClusterEngine, InstanceProps, Login, RotationMultiUserOptions } from './props'; +import { IParameterGroup } from './parameter-group'; +import { BackupProps, InstanceProps, Login, RotationMultiUserOptions } from './props'; import { CfnDBCluster, CfnDBInstance, CfnDBSubnetGroup } from './rds.generated'; /** @@ -18,14 +19,7 @@ export interface DatabaseClusterProps { /** * What kind of database to start */ - readonly engine: DatabaseClusterEngine; - - /** - * What version of the database to start - * - * @default - The default for the engine is used. - */ - readonly engineVersion?: string; + readonly engine: IClusterEngine; /** * How many replicas/instances to create @@ -393,7 +387,6 @@ export class DatabaseCluster extends DatabaseClusterBase { } } - let clusterParameterGroup = props.parameterGroup; const clusterAssociatedRoles: CfnDBCluster.DBClusterRoleProperty[] = []; if (s3ImportRole || s3ExportRole) { if (s3ImportRole) { @@ -402,39 +395,24 @@ export class DatabaseCluster extends DatabaseClusterBase { if (s3ExportRole) { clusterAssociatedRoles.push({ roleArn: s3ExportRole.roleArn }); } - - // MySQL requires the associated roles to be specified as cluster parameters as well, PostgreSQL does not - if (props.engine === DatabaseClusterEngine.AURORA || props.engine === DatabaseClusterEngine.AURORA_MYSQL) { - if (!clusterParameterGroup) { - const parameterGroupFamily = props.engine.parameterGroupFamily(props.engineVersion); - if (!parameterGroupFamily) { - throw new Error(`No parameter group family found for database engine ${props.engine.name} with version ${props.engineVersion}.` + - 'Failed to set the correct cluster parameters for s3 import and export roles.'); - } - clusterParameterGroup = new ClusterParameterGroup(this, 'ClusterParameterGroup', { - family: parameterGroupFamily, - }); - } - - if (clusterParameterGroup instanceof ClusterParameterGroup) { // ignore imported ClusterParameterGroup - if (s3ImportRole) { - clusterParameterGroup.addParameter('aurora_load_from_s3_role', s3ImportRole.roleArn); - } - if (s3ExportRole) { - clusterParameterGroup.addParameter('aurora_select_into_s3_role', s3ExportRole.roleArn); - } - } - } } + // bind the engine to the Cluster + const clusterEngineBindConfig = props.engine.bindToCluster(this, { + s3ImportRole, + s3ExportRole, + parameterGroup: props.parameterGroup, + }); + const clusterParameterGroup = props.parameterGroup ?? clusterEngineBindConfig.parameterGroup; + const cluster = new CfnDBCluster(this, 'Resource', { // Basic - engine: props.engine.name, - engineVersion: props.engineVersion, + engine: props.engine.engineType, + engineVersion: props.engine.engineVersion, dbClusterIdentifier: props.clusterIdentifier, dbSubnetGroupName: subnetGroup.ref, vpcSecurityGroupIds: securityGroups.map(sg => sg.securityGroupId), - port: props.port, + port: props.port ?? clusterEngineBindConfig.port, dbClusterParameterGroupName: clusterParameterGroup && clusterParameterGroup.parameterGroupName, associatedRoles: clusterAssociatedRoles.length > 0 ? clusterAssociatedRoles : undefined, // Admin @@ -493,6 +471,7 @@ export class DatabaseCluster extends DatabaseClusterBase { }); } + const instanceType = props.instanceProps.instanceType ?? props.engine.defaultInstanceType; for (let i = 0; i < instanceCount; i++) { const instanceIndex = i + 1; @@ -504,12 +483,12 @@ export class DatabaseCluster extends DatabaseClusterBase { const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, { // Link to cluster - engine: props.engine.name, - engineVersion: props.engineVersion, + engine: props.engine.engineType, + engineVersion: props.engine.engineVersion, dbClusterIdentifier: cluster.ref, dbInstanceIdentifier: instanceIdentifier, // Instance properties - dbInstanceClass: databaseInstanceType(props.instanceProps.instanceType), + dbInstanceClass: databaseInstanceType(instanceType), publiclyAccessible, // This is already set on the Cluster. Unclear to me whether it should be repeated or not. Better yes. dbSubnetGroupName: subnetGroup.ref, diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index 83e4a99bc4a07..0d8bdf959396a 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -1,5 +1,7 @@ export * from './cluster'; export * from './cluster-ref'; +export * from './cluster-engine'; +export * from './instance-engine'; export * from './props'; export * from './parameter-group'; export * from './database-secret'; diff --git a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts new file mode 100644 index 0000000000000..cff926b24f1c7 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -0,0 +1,526 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; +import * as core from '@aws-cdk/core'; +import { ParameterGroupFamilyMapping } from './private/parameter-group-family-mapping'; + +/** + * The options passed to {@link IInstanceEngine.bind}. + */ +export interface InstanceEngineBindOptions { + /** + * The timezone of the database, set by the customer. + * + * @default - none (it's an optional field) + */ + readonly timezone?: string; +} + +/** + * The type returned from the {@link IInstanceEngine.bind} method. + * Empty for now, + * but there might be fields added to it in the future. + */ +export interface InstanceEngineConfig { +} + +/** + * Interface representing a database instance (as opposed to cluster) engine. + */ +export interface IInstanceEngine { + /** The type of the engine, for example "mysql". */ + readonly engineType: string; + + /** + * The exact version of the engine that is used, + * for example "5.1.42". + * + * @default - use the default version for this engine type + */ + readonly engineVersion?: string; + + /** The application used by this engine to perform rotation for a single-user scenario. */ + readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + + /** The application used by this engine to perform rotation for a multi-user scenario. */ + readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + + /** + * The default instanceType to use with this engine. + */ + readonly defaultInstanceType: ec2.InstanceType; + + /** + * Method called when the engine is used to create a new instance. + */ + bindToInstance(scope: core.Construct, options: InstanceEngineBindOptions): InstanceEngineConfig; +} + +interface BaseInstanceEngineProps { + readonly engineType: string; + readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + readonly parameterGroupFamilies?: ParameterGroupFamilyMapping[]; + readonly defaultInstanceType?: ec2.InstanceType; + readonly engineVersion?: string; +} + +abstract class BaseInstanceEngine implements IInstanceEngine { + public readonly engineType: string; + public readonly engineVersion?: string; + public readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + public readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + public readonly defaultInstanceType: ec2.InstanceType; + + constructor(props: BaseInstanceEngineProps) { + this.engineType = props.engineType; + this.singleUserRotationApplication = props.singleUserRotationApplication; + this.multiUserRotationApplication = props.multiUserRotationApplication; + this.defaultInstanceType = props.defaultInstanceType ?? ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE); + this.engineVersion = props.engineVersion; + } + + public bindToInstance(_scope: core.Construct, _options: InstanceEngineBindOptions): InstanceEngineConfig { + return { + defaultInstanceType: this.defaultInstanceType, + }; + } +} + +abstract class NonSqlServerInstanceEngine extends BaseInstanceEngine { + public bindToInstance(scope: core.Construct, options: InstanceEngineBindOptions): InstanceEngineConfig { + if (options.timezone) { + throw new Error(`timezone property can be configured only for Microsoft SQL Server, not ${this.engineType}`); + } + return super.bindToInstance(scope, options); + } +} + +/** + * Properties for MariaDB instance engines. + * Used in {@link DatabaseInstanceEngine.mariaDb}. + */ +export interface MariaDbInstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class MariaDbInstanceEngine extends NonSqlServerInstanceEngine { + constructor(props: MariaDbInstanceEngineProps = {}) { + super({ + engineType: 'mariadb', + singleUserRotationApplication: secretsmanager.SecretRotationApplication.MARIADB_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.MARIADB_ROTATION_MULTI_USER, + parameterGroupFamilies: [ + { engineMajorVersion: '10.0', parameterGroupFamily: 'mariadb10.0' }, + { engineMajorVersion: '10.1', parameterGroupFamily: 'mariadb10.1' }, + { engineMajorVersion: '10.2', parameterGroupFamily: 'mariadb10.2' }, + { engineMajorVersion: '10.3', parameterGroupFamily: 'mariadb10.3' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +/** + * Properties for MySQL instance engines. + * Used in {@link DatabaseInstanceEngine.mySql}. + */ +export interface MySqlInstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class MySqlInstanceEngine extends NonSqlServerInstanceEngine { + constructor(props: MySqlInstanceEngineProps = {}) { + super({ + engineType: 'mysql', + singleUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, + parameterGroupFamilies: [ + { engineMajorVersion: '5.6', parameterGroupFamily: 'mysql5.6' }, + { engineMajorVersion: '5.7', parameterGroupFamily: 'mysql5.7' }, + { engineMajorVersion: '8.0', parameterGroupFamily: 'mysql8.0' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +/** + * Properties for PostgreSQL instance engines. + * Used in {@link DatabaseInstanceEngine.postgres}. + */ +export interface PostgresInstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +/** + * The instance engine for PostgreSQL. + */ +class PostgresInstanceEngine extends NonSqlServerInstanceEngine { + constructor(props: PostgresInstanceEngineProps = {}) { + super({ + engineType: 'postgres', + singleUserRotationApplication: secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_MULTI_USER, + parameterGroupFamilies: [ + { engineMajorVersion: '9.3', parameterGroupFamily: 'postgres9.3' }, + { engineMajorVersion: '9.4', parameterGroupFamily: 'postgres9.4' }, + { engineMajorVersion: '9.5', parameterGroupFamily: 'postgres9.5' }, + { engineMajorVersion: '9.6', parameterGroupFamily: 'postgres9.6' }, + { engineMajorVersion: '10', parameterGroupFamily: 'postgres10' }, + { engineMajorVersion: '11', parameterGroupFamily: 'postgres11' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +interface OracleInstanceEngineProps { + readonly engineType: string; + readonly engineVersion?: string; + readonly parameterGroupFamilies?: ParameterGroupFamilyMapping[]; +} + +abstract class OracleInstanceEngine extends NonSqlServerInstanceEngine { + constructor(props: OracleInstanceEngineProps) { + super({ + ...props, + singleUserRotationApplication: secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, + }); + } +} + +/** + * Properties for Oracle Standard Edition instance engines. + * Used in {@link DatabaseInstanceEngine.oracleStandardEdition}. + */ +export interface OracleSeInstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class OracleSeInstanceEngine extends OracleInstanceEngine { + constructor(props: OracleSeInstanceEngineProps = {}) { + super({ + engineType: 'oracle-se', + parameterGroupFamilies: [ + { engineMajorVersion: '11.2', parameterGroupFamily: 'oracle-se-11.2' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +/** + * Properties for Oracle Standard Edition 1 instance engines. + * Used in {@link DatabaseInstanceEngine.oracleStandardEdition1}. + */ +export interface OracleSe1InstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class OracleSe1InstanceEngine extends OracleInstanceEngine { + constructor(props: OracleSe1InstanceEngineProps = {}) { + super({ + engineType: 'oracle-se1', + parameterGroupFamilies: [ + { engineMajorVersion: '11.2', parameterGroupFamily: 'oracle-se1-11.2' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +/** + * Properties for Oracle Standard Edition 2 instance engines. + * Used in {@link DatabaseInstanceEngine.oracleStandardEdition2}. + */ +export interface OracleSe2InstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class OracleSe2InstanceEngine extends OracleInstanceEngine { + constructor(props: OracleSe2InstanceEngineProps = {}) { + super({ + engineType: 'oracle-se2', + parameterGroupFamilies: [ + { engineMajorVersion: '12.1', parameterGroupFamily: 'oracle-se2-12.1' }, + { engineMajorVersion: '12.2', parameterGroupFamily: 'oracle-se2-12.2' }, + { engineMajorVersion: '18', parameterGroupFamily: 'oracle-se2-18' }, + { engineMajorVersion: '19', parameterGroupFamily: 'oracle-se2-19' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +/** + * Properties for Oracle Enterprise Edition instance engines. + * Used in {@link DatabaseInstanceEngine.oracleEnterpriseEdition}. + */ +export interface OracleEeInstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class OracleEeInstanceEngine extends OracleInstanceEngine { + constructor(props: OracleEeInstanceEngineProps = {}) { + super({ + engineType: 'oracle-ee', + parameterGroupFamilies: [ + { engineMajorVersion: '11.2', parameterGroupFamily: 'oracle-ee-11.2' }, + { engineMajorVersion: '12.1', parameterGroupFamily: 'oracle-ee-12.1' }, + { engineMajorVersion: '12.2', parameterGroupFamily: 'oracle-ee-12.2' }, + { engineMajorVersion: '18', parameterGroupFamily: 'oracle-ee-18' }, + { engineMajorVersion: '19', parameterGroupFamily: 'oracle-ee-19' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +interface SqlServerInstanceEngineProps { + readonly engineType: string; + readonly engineVersion?: string; + readonly parameterGroupFamilies?: ParameterGroupFamilyMapping[]; + readonly defaultInstanceType?: ec2.InstanceType; +} + +abstract class SqlServerInstanceEngine extends BaseInstanceEngine { + constructor(props: SqlServerInstanceEngineProps) { + super({ + ...props, + singleUserRotationApplication: secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, + multiUserRotationApplication: secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, + }); + } +} + +/** + * Properties for SQL Server Standard Edition instance engines. + * Used in {@link DatabaseInstanceEngine.sqlServerStandardEdition}. + */ +export interface SqlServerSeInstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class SqlServerSeInstanceEngine extends SqlServerInstanceEngine { + constructor(props: SqlServerSeInstanceEngineProps = {}) { + super({ + engineType: 'sqlserver-se', + parameterGroupFamilies: [ + { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-se-11.0' }, + { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-se-12.0' }, + { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-se-13.0' }, + { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-se-14.0' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +/** + * Properties for SQL Server Express Edition instance engines. + * Used in {@link DatabaseInstanceEngine.sqlServerExpressEdition}. + */ +export interface SqlServerExInstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class SqlServerExInstanceEngine extends SqlServerInstanceEngine { + constructor(props: SqlServerExInstanceEngineProps = {}) { + super({ + engineType: 'sqlserver-ex', + parameterGroupFamilies: [ + { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-ex-11.0' }, + { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-ex-12.0' }, + { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-ex-13.0' }, + { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-ex-14.0' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +/** + * Properties for SQL Server Web Edition instance engines. + * Used in {@link DatabaseInstanceEngine.sqlServerWebEdition}. + */ +export interface SqlServerWebInstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class SqlServerWebInstanceEngine extends SqlServerInstanceEngine { + constructor(props: SqlServerWebInstanceEngineProps = {}) { + super({ + engineType: 'sqlserver-web', + parameterGroupFamilies: [ + { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-web-11.0' }, + { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-web-12.0' }, + { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-web-13.0' }, + { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-web-14.0' }, + ], + engineVersion: props.engineVersion, + }); + } +} + +/** + * Properties for SQL Server Enterprise Edition instance engines. + * Used in {@link DatabaseInstanceEngine.sqlServerEnterpriseEdition}. + */ +export interface SqlServerEeInstanceEngineProps { + /** + * The exact version of the engine to use. + * + * @default - no version specified + */ + readonly engineVersion?: string; +} + +class SqlServerEeInstanceEngine extends SqlServerInstanceEngine { + constructor(props: SqlServerEeInstanceEngineProps = {}) { + super({ + engineType: 'sqlserver-ee', + parameterGroupFamilies: [ + { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-ee-11.0' }, + { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-ee-12.0' }, + { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-ee-13.0' }, + { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-ee-14.0' }, + ], + defaultInstanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE), + engineVersion: props.engineVersion, + }); + } +} + +/** + * A database instance engine. Provides mapping to DatabaseEngine used for + * secret rotation. + */ +export class DatabaseInstanceEngine { + public static readonly MARIADB: IInstanceEngine = new MariaDbInstanceEngine(); + + public static readonly MYSQL: IInstanceEngine = new MySqlInstanceEngine(); + + public static readonly ORACLE_EE: IInstanceEngine = new OracleEeInstanceEngine(); + + public static readonly ORACLE_SE2: IInstanceEngine = new OracleSe2InstanceEngine(); + + public static readonly ORACLE_SE1: IInstanceEngine = new OracleSe1InstanceEngine(); + + public static readonly ORACLE_SE: IInstanceEngine = new OracleSeInstanceEngine(); + + public static readonly POSTGRES: IInstanceEngine = new PostgresInstanceEngine(); + + public static readonly SQL_SERVER_EE: IInstanceEngine = new SqlServerEeInstanceEngine(); + + public static readonly SQL_SERVER_SE: IInstanceEngine = new SqlServerSeInstanceEngine(); + + public static readonly SQL_SERVER_EX: IInstanceEngine = new SqlServerExInstanceEngine(); + + public static readonly SQL_SERVER_WEB: IInstanceEngine = new SqlServerWebInstanceEngine(); + + /** Creates a new MariaDB instance engine. */ + public static mariaDb(props: MariaDbInstanceEngineProps): IInstanceEngine { + return new MariaDbInstanceEngine(props); + } + + /** Creates a new MySQL instance engine. */ + public static mySql(props: MySqlInstanceEngineProps): IInstanceEngine { + return new MySqlInstanceEngine(props); + } + + /** Creates a new PostgreSQL instance engine. */ + public static postgres(props: PostgresInstanceEngineProps): IInstanceEngine { + return new PostgresInstanceEngine(props); + } + + /** Creates a new Oracle Standard Edition instance engine. */ + public static oracleStandardEdition(props: OracleSeInstanceEngineProps): IInstanceEngine { + return new OracleSeInstanceEngine(props); + } + + /** Creates a new Oracle Standard Edition 1 instance engine. */ + public static oracleStandardEdition1(props: OracleSe1InstanceEngineProps): IInstanceEngine { + return new OracleSe1InstanceEngine(props); + } + + /** Creates a new Oracle Standard Edition 1 instance engine. */ + public static oracleStandardEdition2(props: OracleSe2InstanceEngineProps): IInstanceEngine { + return new OracleSe2InstanceEngine(props); + } + + /** Creates a new Oracle Enterprise Edition instance engine. */ + public static oracleEnterpriseEdition(props: OracleEeInstanceEngineProps): IInstanceEngine { + return new OracleEeInstanceEngine(props); + } + + /** Creates a new SQL Server Standard Edition instance engine. */ + public static sqlServerStandardEdition(props: SqlServerSeInstanceEngineProps): IInstanceEngine { + return new SqlServerSeInstanceEngine(props); + } + + /** Creates a new SQL Server Express Edition instance engine. */ + public static sqlServerExpressEdition(props: SqlServerExInstanceEngineProps): IInstanceEngine { + return new SqlServerExInstanceEngine(props); + } + + /** Creates a new SQL Server Web Edition instance engine. */ + public static sqlServerWebEdition(props: SqlServerWebInstanceEngineProps): IInstanceEngine { + return new SqlServerWebInstanceEngine(props); + } + + /** Creates a new SQL Server Enterprise Edition instance engine. */ + public static sqlServerEnterpriseEdition(props: SqlServerEeInstanceEngineProps): IInstanceEngine { + return new SqlServerEeInstanceEngine(props); + } +} diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 7af0ebe13a58c..39c04fcdb931b 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -8,9 +8,10 @@ import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { CfnDeletionPolicy, Construct, Duration, IResource, Lazy, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; +import { IInstanceEngine } from './instance-engine'; import { IOptionGroup } from './option-group'; import { IParameterGroup } from './parameter-group'; -import { DatabaseClusterEngine, RotationMultiUserOptions } from './props'; +import { RotationMultiUserOptions } from './props'; import { CfnDBInstance, CfnDBInstanceProps, CfnDBSubnetGroup } from './rds.generated'; /** @@ -148,90 +149,6 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase } } -/** - * A database instance engine. Provides mapping to DatabaseEngine used for - * secret rotation. - */ -export class DatabaseInstanceEngine extends DatabaseClusterEngine { - /* tslint:disable max-line-length */ - public static readonly MARIADB = new DatabaseInstanceEngine('mariadb', secretsmanager.SecretRotationApplication.MARIADB_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.MARIADB_ROTATION_MULTI_USER, [ - { engineMajorVersion: '10.0', parameterGroupFamily: 'mariadb10.0' }, - { engineMajorVersion: '10.1', parameterGroupFamily: 'mariadb10.1' }, - { engineMajorVersion: '10.2', parameterGroupFamily: 'mariadb10.2' }, - { engineMajorVersion: '10.3', parameterGroupFamily: 'mariadb10.3' }, - ]); - - public static readonly MYSQL = new DatabaseInstanceEngine('mysql', secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, [ - { engineMajorVersion: '5.6', parameterGroupFamily: 'mysql5.6' }, - { engineMajorVersion: '5.7', parameterGroupFamily: 'mysql5.7' }, - { engineMajorVersion: '8.0', parameterGroupFamily: 'mysql8.0' }, - ]); - - public static readonly ORACLE_EE = new DatabaseInstanceEngine('oracle-ee', secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11.2', parameterGroupFamily: 'oracle-ee-11.2' }, - { engineMajorVersion: '12.1', parameterGroupFamily: 'oracle-ee-12.1' }, - { engineMajorVersion: '12.2', parameterGroupFamily: 'oracle-ee-12.2' }, - { engineMajorVersion: '18', parameterGroupFamily: 'oracle-ee-18' }, - { engineMajorVersion: '19', parameterGroupFamily: 'oracle-ee-19' }, - ]); - - public static readonly ORACLE_SE2 = new DatabaseInstanceEngine('oracle-se2', secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, [ - { engineMajorVersion: '12.1', parameterGroupFamily: 'oracle-se2-12.1' }, - { engineMajorVersion: '12.2', parameterGroupFamily: 'oracle-se2-12.2' }, - { engineMajorVersion: '18', parameterGroupFamily: 'oracle-se2-18' }, - { engineMajorVersion: '19', parameterGroupFamily: 'oracle-se2-19' }, - ]); - - public static readonly ORACLE_SE1 = new DatabaseInstanceEngine('oracle-se1', secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11.2', parameterGroupFamily: 'oracle-se1-11.2' }, - ]); - - public static readonly ORACLE_SE = new DatabaseInstanceEngine('oracle-se', secretsmanager.SecretRotationApplication.ORACLE_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.ORACLE_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11.2', parameterGroupFamily: 'oracle-se-11.2' }, - ]); - - public static readonly POSTGRES = new DatabaseInstanceEngine('postgres', secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_MULTI_USER, [ - { engineMajorVersion: '9.3', parameterGroupFamily: 'postgres9.3' }, - { engineMajorVersion: '9.4', parameterGroupFamily: 'postgres9.4' }, - { engineMajorVersion: '9.5', parameterGroupFamily: 'postgres9.5' }, - { engineMajorVersion: '9.6', parameterGroupFamily: 'postgres9.6' }, - { engineMajorVersion: '10', parameterGroupFamily: 'postgres10' }, - { engineMajorVersion: '11', parameterGroupFamily: 'postgres11' }, - ]); - - public static readonly SQL_SERVER_EE = new DatabaseInstanceEngine('sqlserver-ee', secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-ee-11.0' }, - { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-ee-12.0' }, - { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-ee-13.0' }, - { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-ee-14.0' }, - ]); - - public static readonly SQL_SERVER_SE = new DatabaseInstanceEngine('sqlserver-se', secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-se-11.0' }, - { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-se-12.0' }, - { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-se-13.0' }, - { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-se-14.0' }, - ]); - - public static readonly SQL_SERVER_EX = new DatabaseInstanceEngine('sqlserver-ex', secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-ex-11.0' }, - { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-ex-12.0' }, - { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-ex-13.0' }, - { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-ex-14.0' }, - ]); - - public static readonly SQL_SERVER_WEB = new DatabaseInstanceEngine('sqlserver-web', secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.SQLSERVER_ROTATION_MULTI_USER, [ - { engineMajorVersion: '11', parameterGroupFamily: 'sqlserver-web-11.0' }, - { engineMajorVersion: '12', parameterGroupFamily: 'sqlserver-web-12.0' }, - { engineMajorVersion: '13', parameterGroupFamily: 'sqlserver-web-13.0' }, - { engineMajorVersion: '14', parameterGroupFamily: 'sqlserver-web-14.0' }, - ]); - /* tslint:enable max-line-length */ - - /** To make it a compile-time error to pass a DatabaseClusterEngine where a DatabaseInstanceEngine is expected. */ - public readonly isDatabaseInstanceEngine = true; -} - /** * The license model. */ @@ -310,11 +227,6 @@ export enum PerformanceInsightRetention { * Construction properties for a DatabaseInstanceNew */ export interface DatabaseInstanceNewProps { - /** - * The name of the compute and memory capacity classes. - */ - readonly instanceType: ec2.InstanceType; - /** * Specifies if the database instance is a multiple Availability Zone deployment. * @@ -559,6 +471,8 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData public readonly connections: ec2.Connections; + protected abstract readonly instanceType: ec2.InstanceType; + protected readonly vpcPlacement?: ec2.SubnetSelection; protected readonly newCfnProps: CfnDBInstanceProps; @@ -610,7 +524,7 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData availabilityZone: props.multiAz ? undefined : props.availabilityZone, backupRetentionPeriod: props.backupRetention ? props.backupRetention.toDays() : undefined, copyTagsToSnapshot: props.copyTagsToSnapshot !== undefined ? props.copyTagsToSnapshot : true, - dbInstanceClass: `db.${props.instanceType}`, + dbInstanceClass: Lazy.stringValue({ produce: () => `db.${this.instanceType}` }), dbInstanceIdentifier: props.instanceIdentifier, dbSubnetGroupName: subnetGroup.ref, deleteAutomatedBackups: props.deleteAutomatedBackups, @@ -660,22 +574,21 @@ export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { /** * The database engine. */ - readonly engine: DatabaseInstanceEngine; + readonly engine: IInstanceEngine; /** - * The license model. + * The name of the compute and memory capacity for the instance. * - * @default - RDS default license model + * @default - the default instance type from the engine will be used */ - readonly licenseModel?: LicenseModel; + readonly instanceType?: ec2.InstanceType; /** - * The engine version. To prevent automatic upgrades, be sure to specify the - * full version number. + * The license model. * - * @default - RDS default engine version + * @default - RDS default license model */ - readonly engineVersion?: string; + readonly licenseModel?: LicenseModel; /** * Whether to allow major version upgrades. @@ -737,6 +650,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa public abstract readonly secret?: secretsmanager.ISecret; protected readonly sourceCfnProps: CfnDBInstanceProps; + protected readonly instanceType: ec2.InstanceType; private readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; private readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; @@ -747,11 +661,8 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa this.singleUserRotationApplication = props.engine.singleUserRotationApplication; this.multiUserRotationApplication = props.engine.multiUserRotationApplication; - const timezoneSupport = [ DatabaseInstanceEngine.SQL_SERVER_EE, DatabaseInstanceEngine.SQL_SERVER_EX, - DatabaseInstanceEngine.SQL_SERVER_SE, DatabaseInstanceEngine.SQL_SERVER_WEB ]; - if (props.timezone && !timezoneSupport.includes(props.engine)) { - throw new Error(`timezone property can be configured only for Microsoft SQL Server, not ${props.engine.name}`); - } + props.engine.bindToInstance(this, props); + this.instanceType = props.instanceType ?? props.engine.defaultInstanceType; this.sourceCfnProps = { ...this.newCfnProps, @@ -759,8 +670,8 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa allowMajorVersionUpgrade: props.allowMajorVersionUpgrade, dbName: props.databaseName, dbParameterGroupName: props.parameterGroup && props.parameterGroup.parameterGroupName, - engine: props.engine.name, - engineVersion: props.engineVersion, + engine: props.engine.engineType, + engineVersion: props.engine.engineVersion, licenseModel: props.licenseModel, timezone: props.timezone, }; @@ -996,6 +907,11 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme * Construction properties for a DatabaseInstanceReadReplica. */ export interface DatabaseInstanceReadReplicaProps extends DatabaseInstanceNewProps { + /** + * The name of the compute and memory capacity classes. + */ + readonly instanceType: ec2.InstanceType; + /** * The source database instance. * @@ -1030,6 +946,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements public readonly dbInstanceEndpointAddress: string; public readonly dbInstanceEndpointPort: string; public readonly instanceEndpoint: Endpoint; + protected readonly instanceType: ec2.InstanceType; constructor(scope: Construct, id: string, props: DatabaseInstanceReadReplicaProps) { super(scope, id, props); @@ -1042,6 +959,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements storageEncrypted: props.storageEncryptionKey ? true : props.storageEncrypted, }); + this.instanceType = props.instanceType; this.instanceIdentifier = instance.ref; this.dbInstanceEndpointAddress = instance.attrEndpointAddress; this.dbInstanceEndpointPort = instance.attrEndpointPort; diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index 19ca1781bccda..a12b5bb44b13e 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import { Construct, IResource, Resource } from '@aws-cdk/core'; -import { DatabaseInstanceEngine } from './instance'; +import { IInstanceEngine } from './instance-engine'; import { CfnOptionGroup } from './rds.generated'; /** @@ -62,13 +62,7 @@ export interface OptionGroupProps { /** * The database engine that this option group is associated with. */ - readonly engine: DatabaseInstanceEngine; - - /** - * The major version number of the database engine that this option group - * is associated with. - */ - readonly majorEngineVersion: string; + readonly engine: IInstanceEngine; /** * A description of the option group. @@ -110,10 +104,15 @@ export class OptionGroup extends Resource implements IOptionGroup { constructor(scope: Construct, id: string, props: OptionGroupProps) { super(scope, id); + const majorEngineVersion = props.engine.engineVersion; + if (!majorEngineVersion) { + throw new Error('OptionGroup can only be used with an engine that has a specific version. ' + + `Use the withVersion method to specify the version for engine ${props.engine.engineType}`); + } const optionGroup = new CfnOptionGroup(this, 'Resource', { - engineName: props.engine.name, - majorEngineVersion: props.majorEngineVersion, - optionGroupDescription: props.description || `Option group for ${props.engine.name} ${props.majorEngineVersion}`, + engineName: props.engine.engineType, + majorEngineVersion, + optionGroupDescription: props.description || `Option group for ${props.engine.engineType} ${majorEngineVersion}`, optionConfigurations: this.renderConfigurations(props.configurations), }); diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts index 40cddd310fa0c..4d2899119ae63 100644 --- a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -11,6 +11,17 @@ export interface IParameterGroup extends IResource { * @attribute */ readonly parameterGroupName: string; + + /** + * Adds a parameter to this group. + * If this is an imported parameter group, + * this method does nothing. + * + * @returns true if the parameter was actually added + * (i.e., this ParameterGroup is not imported), + * false otherwise + */ + addParameter(key: string, value: string): boolean; } /** @@ -23,6 +34,8 @@ abstract class ParameterGroupBase extends Resource implements IParameterGroup { public static fromParameterGroupName(scope: Construct, id: string, parameterGroupName: string): IParameterGroup { class Import extends Resource implements IParameterGroup { public readonly parameterGroupName = parameterGroupName; + + public addParameter(): boolean { return false; } } return new Import(scope, id); } @@ -35,7 +48,13 @@ abstract class ParameterGroupBase extends Resource implements IParameterGroup { /** * Parameters of the parameter group */ - protected parameters?: { [key: string]: string } = {}; + protected readonly parameters: { [key: string]: string }; + + constructor(scope: Construct, id: string, parameters: { [key: string]: string } | undefined) { + super(scope, id); + + this.parameters = parameters ?? {}; + } /** * Add a parameter to this parameter group @@ -43,11 +62,9 @@ abstract class ParameterGroupBase extends Resource implements IParameterGroup { * @param key The key of the parameter to be added * @param value The value of the parameter to be added */ - public addParameter(key: string, value: string) { - if (!this.parameters) { - this.parameters = {}; - } + public addParameter(key: string, value: string): boolean { this.parameters[key] = value; + return true; } } @@ -87,9 +104,7 @@ export class ParameterGroup extends ParameterGroupBase { public readonly parameterGroupName: string; constructor(scope: Construct, id: string, props: ParameterGroupProps) { - super(scope, id); - - this.parameters = props.parameters ? props.parameters : {}; + super(scope, id, props.parameters); const resource = new CfnDBParameterGroup(this, 'Resource', { description: props.description || `Parameter group for ${props.family}`, @@ -120,9 +135,7 @@ export class ClusterParameterGroup extends ParameterGroupBase { public readonly parameterGroupName: string; constructor(scope: Construct, id: string, props: ClusterParameterGroupProps) { - super(scope, id); - - this.parameters = props.parameters ? props.parameters : {}; + super(scope, id, props.parameters); const resource = new CfnDBClusterParameterGroup(this, 'Resource', { description: props.description || `Cluster parameter group for ${props.family}`, diff --git a/packages/@aws-cdk/aws-rds/lib/private/parameter-group-family-mapping.ts b/packages/@aws-cdk/aws-rds/lib/private/parameter-group-family-mapping.ts new file mode 100644 index 0000000000000..ec1eb19a27547 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/private/parameter-group-family-mapping.ts @@ -0,0 +1,14 @@ +/** + * Engine major version and parameter group family pairs. + */ +export interface ParameterGroupFamilyMapping { + /** + * The engine major version name + */ + readonly engineMajorVersion: string; + + /** + * The parameter group family name + */ + readonly parameterGroupFamily: string +} diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index e52841da926c0..e7442cc7e34fc 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -2,100 +2,18 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { Duration, SecretValue } from '@aws-cdk/core'; -import { IParameterGroup } from './parameter-group'; -import { compare } from './private/version'; - -/** - * Engine major version and parameter group family pairs. - */ -export interface ParameterGroupFamily { - /** - * The engine major version name - */ - readonly engineMajorVersion: string; - - /** - * The parameter group family name - */ - readonly parameterGroupFamily: string -} - -/** - * A database cluster engine. Provides mapping to the serverless application - * used for secret rotation. - */ -export class DatabaseClusterEngine { - /* tslint:disable max-line-length */ - public static readonly AURORA = new DatabaseClusterEngine('aurora', secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, [ - { engineMajorVersion: '5.6', parameterGroupFamily: 'aurora5.6' }, - ]); - - public static readonly AURORA_MYSQL = new DatabaseClusterEngine('aurora-mysql', secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, [ - { engineMajorVersion: '5.7', parameterGroupFamily: 'aurora-mysql5.7' }, - ]); - - public static readonly AURORA_POSTGRESQL = new DatabaseClusterEngine('aurora-postgresql', secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_MULTI_USER, [ - { engineMajorVersion: '9.6', parameterGroupFamily: 'aurora-postgresql9.6'}, - { engineMajorVersion: '10', parameterGroupFamily: 'aurora-postgresql10' }, - { engineMajorVersion: '11', parameterGroupFamily: 'aurora-postgresql11'}, - ]); - /* tslint:enable max-line-length */ - - /** - * The engine. - */ - public readonly name: string; - - /** - * The single user secret rotation application. - */ - public readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; - - /** - * The multi user secret rotation application. - */ - public readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; - - private readonly parameterGroupFamilies?: ParameterGroupFamily[]; - - // tslint:disable-next-line max-line-length - constructor(name: string, singleUserRotationApplication: secretsmanager.SecretRotationApplication, multiUserRotationApplication: secretsmanager.SecretRotationApplication, parameterGroupFamilies?: ParameterGroupFamily[]) { - this.name = name; - this.singleUserRotationApplication = singleUserRotationApplication; - this.multiUserRotationApplication = multiUserRotationApplication; - this.parameterGroupFamilies = parameterGroupFamilies; - } - - /** - * Get the latest parameter group family for this engine. Latest is determined using semver on the engine major version. - * When `engineVersion` is specified, return the parameter group family corresponding to that engine version. - * Return undefined if no parameter group family is defined for this engine or for the requested `engineVersion`. - */ - public parameterGroupFamily(engineVersion?: string): string | undefined { - if (this.parameterGroupFamilies === undefined) { return undefined; } - if (engineVersion) { - const family = this.parameterGroupFamilies.find(x => engineVersion.startsWith(x.engineMajorVersion)); - if (family) { - return family.parameterGroupFamily; - } - } else if (this.parameterGroupFamilies.length > 0) { - const sorted = this.parameterGroupFamilies.slice().sort((a, b) => { - return compare(a.engineMajorVersion, b.engineMajorVersion); - }).reverse(); - return sorted[0].parameterGroupFamily; - } - return undefined; - } -} +import { IParameterGroup} from './parameter-group'; /** * Instance properties for database instances */ export interface InstanceProps { /** - * What type of instance to start for the replicas + * What type of instance to start for the replicas. + * + * @default - the default instance type for the engine will be used */ - readonly instanceType: ec2.InstanceType; + readonly instanceType?: ec2.InstanceType; /** * What subnets to run the RDS instances in. diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts index d37fd89f9b935..4829e2b72dc47 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -25,8 +25,9 @@ class DatabaseInstanceStack extends cdk.Stack { /// Add XMLDB and OEM with option group const optionGroup = new rds.OptionGroup(this, 'OptionGroup', { - engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: rds.DatabaseInstanceEngine.oracleStandardEdition1({ + engineVersion: '11.2', + }), configurations: [ { name: 'XMLDB', diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster-engine.ts b/packages/@aws-cdk/aws-rds/test/test.cluster-engine.ts new file mode 100644 index 0000000000000..45f59574d6768 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/test.cluster-engine.ts @@ -0,0 +1,113 @@ +import { Test } from 'nodeunit'; +import { DatabaseClusterEngine } from '../lib'; + +export = { + 'cluster parameter group correctly determined for AURORA'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.AURORA; + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora5.6'); + + test.done(); + }, + + 'cluster parameter group correctly determined for AURORA_MYSQL'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.AURORA_MYSQL; + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora-mysql5.7'); + + test.done(); + }, + + 'cluster parameter group correctly determined for AURORA_POSTGRESQL'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.AURORA_POSTGRESQL; + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora-postgresql11'); + + test.done(); + }, + + 'cluster parameter group correctly determined for AURORA and given version'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.defaultAurora({ + engineVersion: '5.6.mysql_aurora.1.22.2', + }); + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora5.6'); + + test.done(); + }, + + 'cluster parameter group correctly determined for AURORA_MYSQL and given version'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.auroraMySql({ + engineVersion: '5.7.mysql_aurora.2.07.1', + }); + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora-mysql5.7'); + + test.done(); + }, + + 'cluster parameter group correctly determined for AURORA_POSTGRESQL and given version'(test: Test) { + // GIVEN + const engine = DatabaseClusterEngine.auroraPostgres({ + engineVersion: '11.6', + }); + + // WHEN + const family = engine.parameterGroupFamily; + + // THEN + test.equals(family, 'aurora-postgresql11'); + + test.done(); + }, + + 'parameter group family'(test: Test) { + // the PostgreSQL engine knows about the following major versions: 9.6, 10 and 11 + + test.throws(() => { + DatabaseClusterEngine.auroraPostgres({ engineVersion: '8' }); + }, /No parameter group family found for database engine aurora-postgresql with version 8/); + + test.throws(() => { + DatabaseClusterEngine.auroraPostgres({ engineVersion: '9' }); + }, /No parameter group family found for database engine aurora-postgresql with version 9/); + + test.throws(() => { + DatabaseClusterEngine.auroraPostgres({ engineVersion: '9.7' }); + }, /No parameter group family found for database engine aurora-postgresql with version 9\.7/); + + test.equals(DatabaseClusterEngine.auroraPostgres({ engineVersion: '9.6' }).parameterGroupFamily, + 'aurora-postgresql9.6'); + test.equals(DatabaseClusterEngine.auroraPostgres({ engineVersion: '9.6.1' }).parameterGroupFamily, + 'aurora-postgresql9.6'); + test.equals(DatabaseClusterEngine.auroraPostgres({ engineVersion: '10.0' }).parameterGroupFamily, + 'aurora-postgresql10'); + + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index 8351399263c6a..b53f9b96d59d9 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -299,8 +299,9 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { - engine: DatabaseClusterEngine.AURORA_MYSQL, - engineVersion: '5.7.mysql_aurora.2.04.4', + engine: DatabaseClusterEngine.auroraMySql({ + engineVersion: '5.7.mysql_aurora.2.04.4', + }), masterUser: { username: 'admin', }, @@ -326,8 +327,9 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { - engine: DatabaseClusterEngine.AURORA_POSTGRESQL, - engineVersion: '10.7', + engine: DatabaseClusterEngine.auroraPostgres({ + engineVersion: '10.7', + }), masterUser: { username: 'admin', }, @@ -954,7 +956,7 @@ export = { test.done(); }, - 'PostgreSQL cluster with s3 export buckets does not generate custom parameter group'(test: Test) { + 'PostgreSQL cluster with s3 export buckets does not generate custom parameter group and specifies the correct port'(test: Test) { // GIVEN const stack = testStack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -976,7 +978,7 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::RDS::DBCluster', { + expect(stack).to(haveResourceLike('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -985,6 +987,36 @@ export = { ], }, }], + DBClusterParameterGroupName: 'default.aurora-postgresql11', + Port: 5432, + })); + + expect(stack).notTo(haveResource('AWS::RDS::DBClusterParameterGroup')); + + test.done(); + }, + + 'MySQL cluster without S3 exports or imports references the correct default ParameterGroup'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + instances: 1, + masterUser: { + username: 'admin', + }, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::RDS::DBCluster', { + DBClusterParameterGroupName: 'default.aurora-mysql5.7', })); expect(stack).notTo(haveResource('AWS::RDS::DBClusterParameterGroup')); diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index c22f926c95fbe..844718faa3416 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -195,8 +195,9 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const optionGroup = new rds.OptionGroup(stack, 'OptionGroup', { - engine: rds.DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: rds.DatabaseInstanceEngine.oracleStandardEdition1({ + engineVersion: '11.2', + }), configurations: [ { name: 'XMLDB', @@ -691,7 +692,7 @@ export = { // THEN tzSupportedEngines.forEach((engine) => { - test.ok(new rds.DatabaseInstance(stack, `${engine.name}-db`, { + test.ok(new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), masterUsername: 'master', @@ -701,7 +702,7 @@ export = { }); tzUnsupportedEngines.forEach((engine) => { - test.throws(() => new rds.DatabaseInstance(stack, `${engine.name}-db`, { + test.throws(() => new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), masterUsername: 'master', diff --git a/packages/@aws-cdk/aws-rds/test/test.option-group.ts b/packages/@aws-cdk/aws-rds/test/test.option-group.ts index d61b1aae06e52..ad989dea6f433 100644 --- a/packages/@aws-cdk/aws-rds/test/test.option-group.ts +++ b/packages/@aws-cdk/aws-rds/test/test.option-group.ts @@ -11,8 +11,9 @@ export = { // WHEN new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: DatabaseInstanceEngine.oracleStandardEdition1({ + engineVersion: '11.2', + }), configurations: [ { name: 'XMLDB', @@ -42,8 +43,9 @@ export = { // WHEN const optionGroup = new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: DatabaseInstanceEngine.oracleStandardEdition({ + engineVersion: '11.2', + }), configurations: [ { name: 'OEM', @@ -56,9 +58,9 @@ export = { // THEN expect(stack).to(haveResource('AWS::RDS::OptionGroup', { - EngineName: 'oracle-se1', + EngineName: 'oracle-se', MajorEngineVersion: '11.2', - OptionGroupDescription: 'Option group for oracle-se1 11.2', + OptionGroupDescription: 'Option group for oracle-se 11.2', OptionConfigurations: [ { OptionName: 'OEM', @@ -100,8 +102,9 @@ export = { // THEN test.throws(() => new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: DatabaseInstanceEngine.oracleStandardEdition2({ + engineVersion: '11.2', + }), configurations: [ { name: 'OEM', diff --git a/packages/@aws-cdk/aws-rds/test/test.props.ts b/packages/@aws-cdk/aws-rds/test/test.props.ts deleted file mode 100644 index 4e2df3c3dda52..0000000000000 --- a/packages/@aws-cdk/aws-rds/test/test.props.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { SecretRotationApplication } from '@aws-cdk/aws-secretsmanager'; -import { Test } from 'nodeunit'; -import { DatabaseClusterEngine } from '../lib'; - -export = { - 'cluster parameter group correctly determined for AURORA'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA; - - // WHEN - const family = engine.parameterGroupFamily(); - - // THEN - test.equals(family, 'aurora5.6'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA_MYSQL'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA_MYSQL; - - // WHEN - const family = engine.parameterGroupFamily(); - - // THEN - test.equals(family, 'aurora-mysql5.7'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA_POSTGRESQL'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA_POSTGRESQL; - - // WHEN - const family = engine.parameterGroupFamily(); - - // THEN - test.equals(family, 'aurora-postgresql11'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA and given version'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA; - - // WHEN - const family = engine.parameterGroupFamily('5.6.mysql_aurora.1.22.2'); - - // THEN - test.equals(family, 'aurora5.6'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA_MYSQL and given version'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA_MYSQL; - - // WHEN - const family = engine.parameterGroupFamily('5.7.mysql_aurora.2.07.1'); - - // THEN - test.equals(family, 'aurora-mysql5.7'); - - test.done(); - }, - - 'cluster parameter group correctly determined for AURORA_POSTGRESQL and given version'(test: Test) { - // GIVEN - const engine = DatabaseClusterEngine.AURORA_POSTGRESQL; - - // WHEN - const family = engine.parameterGroupFamily('11.6'); - - // THEN - test.equals(family, 'aurora-postgresql11'); - - test.done(); - }, - - 'parameter group family'(test: Test) { - // WHEN - const engine1 = new DatabaseClusterEngine( - 'no-parameter-group-family', - SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, - SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER); - const engine2 = new DatabaseClusterEngine( - 'aurora-postgresql', - SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, - SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, - [ - { engineMajorVersion: '1.0', parameterGroupFamily: 'family-1'}, - { engineMajorVersion: '2.0', parameterGroupFamily: 'family-2' }, - ]); - - // THEN - test.equals(engine1.parameterGroupFamily(), undefined); - test.equals(engine1.parameterGroupFamily('1'), undefined); - - test.equals(engine2.parameterGroupFamily('3'), undefined); - test.equals(engine2.parameterGroupFamily('1'), undefined); - test.equals(engine2.parameterGroupFamily('1.1'), undefined); - test.equals(engine2.parameterGroupFamily('1.0'), 'family-1'); - test.equals(engine2.parameterGroupFamily('2.0.2'), 'family-2'); - - test.done(); - }, -};