From 0e460fc964ff0e8269af8927b27f2ee928c1833d 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 setting it to 3306 (which is the MySQL port). Fixes #2213 Fixes #2512 Fixes #4150 Fixes #5126 Fixes #6532 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, in favor of the withVersion() method on IClusterEngine * **rds**: DatabaseInstanceNewProps.instanceClass has been renamed to instanceType * **rds**: DatabaseInstanceSourceProps.engineVersion has been removed, in favor of the withVersion() method on IInstanceEngine * **rds**: the engine property can no longer be passed when creating a DatabaseInstanceReadReplica * **rds**: the property majorEngineVersion can no longer be passed when creating an OptionGroup (instead, the version is taken directly from the passed engine) --- packages/@aws-cdk/aws-rds/README.md | 123 +++++--- .../@aws-cdk/aws-rds/lib/cluster-engine.ts | 286 ++++++++++++++++++ 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 | 269 ++++++++++++++++ packages/@aws-cdk/aws-rds/lib/instance.ts | 132 ++------ 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 +----- .../test/integ.instance.lit.expected.json | 1 + .../aws-rds/test/integ.instance.lit.ts | 8 +- .../@aws-cdk/aws-rds/test/test.cluster.ts | 40 ++- .../@aws-cdk/aws-rds/test/test.instance.ts | 50 ++- .../aws-rds/test/test.option-group.ts | 9 +- packages/@aws-cdk/aws-rds/test/test.props.ts | 53 ++-- 16 files changed, 828 insertions(+), 366 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 diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 070ac5ca1698c..5b3f4eff1fb1b 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.PUBLIC, }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpcSubnets: { - subnetType: ec2.SubnetType.PUBLIC, - }, - 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 instance methods on the passed `engine`: + +```typescript +new rds.DatabaseCluster(this, 'Database', { + engine: rds.DatabaseClusterEngine.AURORA.withVersion1dot17dot9(), + ... +}) +``` + +There's also a `.withVersion(string)` method, +if the version you want to use doesn't have a separate method like `1.17.9` does above. +See [the AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#cfn-rds-dbinstance-engineversion) +for supported version 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, - instanceClass: 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, - instanceClass: 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,18 +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, - instanceClass: 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, - engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), - vpc +new rds.DatabaseInstanceReadReplica(stack, 'ReadReplica', { + sourceDatabaseInstance: sourceInstance, + instanceType: rds.DatabaseInstanceEngine.POSTGRES.defaultInstanceType, + vpc, }); ``` @@ -92,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: @@ -123,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 ``` @@ -155,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(); @@ -182,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..12e4fb2b818e8 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -0,0 +1,286 @@ +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 ClusterEngineBindConfig { + /** + * 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; + + /** + * Returns a new instance of this engine with a particular version specified. + * Calling {@link engineVersion} on the returned instance will always return the provided value. + * + * @param engineVersion the exact engine version, for example "5.7.mysql_aurora.2.03.4" + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html#cfn-rds-dbcluster-engineversion + */ + withVersion(engineVersion: string): IClusterEngine; + + /** + * Method called when the engine is used to create a new cluster. + */ + bindToCluster(scope: core.Construct, options: ClusterEngineBindOptions): ClusterEngineBindConfig; +} + +abstract class AbstractClusterEngine 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( + engineName: string, + needsS3RolesInParameters: boolean, + singleUserRotationApplication: secretsmanager.SecretRotationApplication, + multiUserRotationApplication: secretsmanager.SecretRotationApplication, + parameterGroupFamilies?: ParameterGroupFamilyMapping[], + defaultPort?: number, + engineVersion?: string) { + + this.engineType = engineName; + this.needsS3RolesInParameters = needsS3RolesInParameters; + this.singleUserRotationApplication = singleUserRotationApplication; + this.multiUserRotationApplication = multiUserRotationApplication; + this.parameterGroupFamilies = parameterGroupFamilies; + this.defaultPort = defaultPort; + this.engineVersion = engineVersion; + this.parameterGroupFamily = this.establishParameterGroupFamily(); + } + + public withVersion(engineVersion: string): IClusterEngine { + const self = this; + // put it in a local variable, as it's protected + const defaultParameterGroup = this.defaultParameterGroup; + + class CopyWithVersion extends AbstractClusterEngine { + protected defaultParameterGroup(scope: core.Construct) { + // we need to use call, so that 'this' inside defaultParameterGroup() is resolved correctly + return defaultParameterGroup.call(self, scope); + } + } + + return new CopyWithVersion( + this.engineType, + this.needsS3RolesInParameters, + this.singleUserRotationApplication, + this.multiUserRotationApplication, + this.parameterGroupFamilies, + this.defaultPort, + engineVersion, + ); + } + + public bindToCluster(scope: core.Construct, options: ClusterEngineBindOptions): ClusterEngineBindConfig { + 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, + }; + } + + /** Must be public because of the usage in withVersion */ + 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; + } +} + +/** + * The interface for plain Aurora cluster engines. + */ +export interface IAuroraClusterEngine extends IClusterEngine { + /** Returns an Aurora cluster engine with the engine version set to "5.6.mysql_aurora.1.17.9". */ + withVersion1dot17dot9(): IClusterEngine; +} + +class DefaultAuroraClusterEngine extends AbstractClusterEngine implements IAuroraClusterEngine { + public withVersion1dot17dot9(): IClusterEngine { + return this.withVersion('5.6.mysql_aurora.1.17.9'); + } + + 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; + } +} + +class NonDefaultAuroraClusterEngine extends AbstractClusterEngine { + protected defaultParameterGroup(scope: core.Construct): IParameterGroup | undefined { + return ParameterGroup.fromParameterGroupName(scope, 'AuroraMySqlDefaultParameterGroup', + `default.${this.parameterGroupFamily}`); + } +} + +/** + * A database cluster engine. Provides mapping to the serverless application + * used for secret rotation. + */ +export class DatabaseClusterEngine { + public static readonly AURORA: IAuroraClusterEngine = new DefaultAuroraClusterEngine( + 'aurora', + true, + secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, + [ + { engineMajorVersion: '5.6', parameterGroupFamily: 'aurora5.6' }, + ], + ); + + public static readonly AURORA_MYSQL: IClusterEngine = new NonDefaultAuroraClusterEngine( + 'aurora-mysql', + true, + secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, + [ + { engineMajorVersion: '5.7', parameterGroupFamily: 'aurora-mysql5.7' }, + ], + ); + + public static readonly AURORA_POSTGRESQL: IClusterEngine = new NonDefaultAuroraClusterEngine( + 'aurora-postgresql', + false, + 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' }, + ], + 5432, + ); +} diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 577a5420d0632..076d01b5bfea6 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 @@ -404,7 +398,6 @@ export class DatabaseCluster extends DatabaseClusterBase { } } - let clusterParameterGroup = props.parameterGroup; const clusterAssociatedRoles: CfnDBCluster.DBClusterRoleProperty[] = []; if (s3ImportRole || s3ExportRole) { if (s3ImportRole) { @@ -413,39 +406,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: [this.securityGroupId], - port: props.port, + port: props.port ?? clusterEngineBindConfig.port, dbClusterParameterGroupName: clusterParameterGroup && clusterParameterGroup.parameterGroupName, associatedRoles: clusterAssociatedRoles.length > 0 ? clusterAssociatedRoles : undefined, // Admin @@ -504,6 +482,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; @@ -515,12 +494,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..ebb61bd021816 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -0,0 +1,269 @@ +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 InstanceEngineBindConfig { +} + +/** + * 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; + + /** + * Returns a new instance of this engine with a particular version specified. + * Calling {@link engineVersion} on the returned instance will always return the provided value. + * + * @param engineVersion the exact engine version, for example "5.1.42" + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#cfn-rds-dbinstance-engineversion + */ + withVersion(engineVersion: string): IInstanceEngine; + + /** + * Method called when the engine is used to create a new instance. + */ + bindToInstance(scope: core.Construct, options: InstanceEngineBindOptions): InstanceEngineBindConfig; +} + +abstract class AbstractInstanceEngine 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( + engineName: string, + singleUserRotationApplication: secretsmanager.SecretRotationApplication, + multiUserRotationApplication: secretsmanager.SecretRotationApplication, + _parameterGroupFamilies: ParameterGroupFamilyMapping[], + defaultInstanceType?: ec2.InstanceType, + engineVersion?: string) { + + this.engineType = engineName; + this.singleUserRotationApplication = singleUserRotationApplication; + this.multiUserRotationApplication = multiUserRotationApplication; + this.defaultInstanceType = defaultInstanceType ?? ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE); + this.engineVersion = engineVersion; + } + + public withVersion(engineVersion: string): IInstanceEngine { + class CopyWithVersion extends AbstractInstanceEngine {} + + return new CopyWithVersion( + this.engineType, + this.singleUserRotationApplication, + this.multiUserRotationApplication, + [], + this.defaultInstanceType, + engineVersion, + ); + } + + public bindToInstance(_scope: core.Construct, _options: InstanceEngineBindOptions): InstanceEngineBindConfig { + return { + defaultInstanceType: this.defaultInstanceType, + }; + } +} + +class NonSqlServerInstanceEngine extends AbstractInstanceEngine { + public bindToInstance(scope: core.Construct, options: InstanceEngineBindOptions): InstanceEngineBindConfig { + if (options.timezone) { + throw new Error(`timezone property can be configured only for Microsoft SQL Server, not ${this.engineType}`); + } + return super.bindToInstance(scope, options); + } +} + +class SqlServerInstanceEngine extends AbstractInstanceEngine { +} + +/** + * The instance engine for PostgreSQL. + */ +export interface IPostgresInstanceEngine extends IInstanceEngine { + /** Returns a Postgres instance engine with engine version set to "9.4.26". */ + withVersion9dot4dot26(): IInstanceEngine; +} + +class PostgresInstanceEngine extends NonSqlServerInstanceEngine implements IPostgresInstanceEngine { + public withVersion9dot4dot26(): IInstanceEngine { + return this.withVersion('9.4.26'); + } +} + +/** + * A database instance engine. Provides mapping to DatabaseEngine used for + * secret rotation. + */ +export class DatabaseInstanceEngine { + public static readonly MARIADB: IInstanceEngine = new NonSqlServerInstanceEngine( + '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: IInstanceEngine = new NonSqlServerInstanceEngine( + '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: IInstanceEngine = new NonSqlServerInstanceEngine( + '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: IInstanceEngine = new NonSqlServerInstanceEngine( + '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: IInstanceEngine = new NonSqlServerInstanceEngine( + '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: IInstanceEngine = new NonSqlServerInstanceEngine( + '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: IPostgresInstanceEngine = new PostgresInstanceEngine( + '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: IInstanceEngine = new SqlServerInstanceEngine( + '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' }, + ], + ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE), + ); + + public static readonly SQL_SERVER_SE: IInstanceEngine = new SqlServerInstanceEngine( + '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: IInstanceEngine = new SqlServerInstanceEngine( + '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: IInstanceEngine = new SqlServerInstanceEngine( + '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' }, + ], + ); +} diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 5ed0925bf5d7d..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 instanceClass: 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.instanceClass}`, + 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, }; @@ -995,7 +906,12 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme /** * Construction properties for a DatabaseInstanceReadReplica. */ -export interface DatabaseInstanceReadReplicaProps extends DatabaseInstanceSourceProps { +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 95e04ec684069..e69f43465a989 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.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json index d5c7708151b53..5a7a3ea8cc36c 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json @@ -645,6 +645,7 @@ ], "EnablePerformanceInsights": true, "Engine": "oracle-se1", + "EngineVersion": "11.2", "Iops": 1000, "LicenseModel": "bring-your-own-license", "MasterUsername": { 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 f386c04b0a1d6..58ec085b7eb8c 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -15,6 +15,7 @@ class DatabaseInstanceStack extends cdk.Stack { const vpc = new ec2.Vpc(this, 'VPC', { maxAzs: 2 }); /// !show + const databaseEngine = rds.DatabaseInstanceEngine.ORACLE_SE1.withVersion('11.2'); // Set open cursors with parameter group const parameterGroup = new rds.ParameterGroup(this, 'ParameterGroup', { family: 'oracle-se1-11.2', @@ -25,8 +26,7 @@ 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: databaseEngine, configurations: [ { name: 'XMLDB', @@ -44,9 +44,9 @@ class DatabaseInstanceStack extends cdk.Stack { // Database instance with production values const instance = new rds.DatabaseInstance(this, 'Instance', { - engine: rds.DatabaseInstanceEngine.ORACLE_SE1, + engine: databaseEngine, licenseModel: rds.LicenseModel.BRING_YOUR_OWN_LICENSE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM), multiAz: true, storageType: rds.StorageType.IO1, masterUsername: 'syscdk', diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index 597027e267f2e..2d5855fea1224 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -299,8 +299,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { - engine: DatabaseClusterEngine.AURORA_MYSQL, - engineVersion: '5.7.mysql_aurora.2.04.4', + engine: DatabaseClusterEngine.AURORA_MYSQL.withVersion('5.7.mysql_aurora.2.04.4'), masterUser: { username: 'admin', }, @@ -326,8 +325,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { - engine: DatabaseClusterEngine.AURORA_POSTGRESQL, - engineVersion: '10.7', + engine: DatabaseClusterEngine.AURORA_POSTGRESQL.withVersion('10.7'), masterUser: { username: 'admin', }, @@ -954,7 +952,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 +974,7 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::RDS::DBCluster', { + expect(stack).to(haveResourceLike('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -985,6 +983,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 baefed5b6b157..4b8839bf77553 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -18,7 +18,7 @@ export = { new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.ORACLE_SE1, licenseModel: rds.LicenseModel.BRING_YOUR_OWN_LICENSE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MEDIUM), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MEDIUM), multiAz: true, storageType: rds.StorageType.IO1, masterUsername: 'syscdk', @@ -195,8 +195,7 @@ 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.ORACLE_SE1.withVersion('11.2'), configurations: [ { name: 'XMLDB', @@ -215,7 +214,7 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', masterUserPassword: cdk.SecretValue.plainText('tooshort'), vpc, @@ -244,7 +243,7 @@ export = { new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, }); @@ -264,7 +263,7 @@ export = { test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, generateMasterUserPassword: true, }), '`masterUsername` must be specified when `generateMasterUserPassword` is set to true.'); @@ -281,7 +280,7 @@ export = { test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, masterUsername: 'superadmin', }), 'Cannot specify `masterUsername` when `generateMasterUserPassword` is set to false.'); @@ -298,7 +297,7 @@ export = { test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, masterUserPassword: cdk.SecretValue.plainText('supersecret'), generateMasterUserPassword: true, @@ -313,7 +312,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const sourceInstance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -321,8 +320,7 @@ export = { // WHEN new rds.DatabaseInstanceReadReplica(stack, 'ReadReplica', { sourceDatabaseInstance: sourceInstance, - engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, }); @@ -354,7 +352,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -421,7 +419,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -474,7 +472,7 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -499,7 +497,7 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, }); @@ -530,7 +528,7 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, backupRetention: cdk.Duration.seconds(0), @@ -583,7 +581,7 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, monitoringInterval: cdk.Duration.minutes(1), @@ -612,7 +610,7 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, securityGroups: [securityGroup], @@ -649,7 +647,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', masterUserPassword: cdk.SecretValue.plainText('tooshort'), vpc, @@ -667,7 +665,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', vpc, }); @@ -692,9 +690,9 @@ export = { // THEN tzSupportedEngines.forEach((engine) => { - test.ok(new rds.DatabaseInstance(stack, `${engine.name}-db`, { + test.ok(new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), masterUsername: 'master', timezone: 'Europe/Zurich', vpc, @@ -702,9 +700,9 @@ export = { }); tzUnsupportedEngines.forEach((engine) => { - test.throws(() => new rds.DatabaseInstance(stack, `${engine.name}-db`, { + test.throws(() => new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), masterUsername: 'master', timezone: 'Europe/Zurich', vpc, @@ -723,7 +721,7 @@ export = { new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, maxAllocatedStorage: 200, }); @@ -744,7 +742,7 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, backupRetention: cdk.Duration.seconds(0), 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..97600c670097a 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,7 @@ export = { // WHEN new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: DatabaseInstanceEngine.ORACLE_SE1.withVersion('11.2'), configurations: [ { name: 'XMLDB', @@ -42,8 +41,7 @@ export = { // WHEN const optionGroup = new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: DatabaseInstanceEngine.ORACLE_SE1.withVersion('11.2'), configurations: [ { name: 'OEM', @@ -100,8 +98,7 @@ export = { // THEN test.throws(() => new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.ORACLE_SE1, - majorEngineVersion: '11.2', + engine: DatabaseInstanceEngine.ORACLE_SE1.withVersion('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 index 4e2df3c3dda52..be961a4c8f3a4 100644 --- a/packages/@aws-cdk/aws-rds/test/test.props.ts +++ b/packages/@aws-cdk/aws-rds/test/test.props.ts @@ -1,4 +1,3 @@ -import { SecretRotationApplication } from '@aws-cdk/aws-secretsmanager'; import { Test } from 'nodeunit'; import { DatabaseClusterEngine } from '../lib'; @@ -8,7 +7,7 @@ export = { const engine = DatabaseClusterEngine.AURORA; // WHEN - const family = engine.parameterGroupFamily(); + const family = engine.parameterGroupFamily; // THEN test.equals(family, 'aurora5.6'); @@ -21,7 +20,7 @@ export = { const engine = DatabaseClusterEngine.AURORA_MYSQL; // WHEN - const family = engine.parameterGroupFamily(); + const family = engine.parameterGroupFamily; // THEN test.equals(family, 'aurora-mysql5.7'); @@ -34,7 +33,7 @@ export = { const engine = DatabaseClusterEngine.AURORA_POSTGRESQL; // WHEN - const family = engine.parameterGroupFamily(); + const family = engine.parameterGroupFamily; // THEN test.equals(family, 'aurora-postgresql11'); @@ -47,7 +46,7 @@ export = { const engine = DatabaseClusterEngine.AURORA; // WHEN - const family = engine.parameterGroupFamily('5.6.mysql_aurora.1.22.2'); + const family = engine.withVersion('5.6.mysql_aurora.1.22.2').parameterGroupFamily; // THEN test.equals(family, 'aurora5.6'); @@ -60,7 +59,7 @@ export = { const engine = DatabaseClusterEngine.AURORA_MYSQL; // WHEN - const family = engine.parameterGroupFamily('5.7.mysql_aurora.2.07.1'); + const family = engine.withVersion('5.7.mysql_aurora.2.07.1').parameterGroupFamily; // THEN test.equals(family, 'aurora-mysql5.7'); @@ -73,7 +72,7 @@ export = { const engine = DatabaseClusterEngine.AURORA_POSTGRESQL; // WHEN - const family = engine.parameterGroupFamily('11.6'); + const family = engine.withVersion('11.6').parameterGroupFamily; // THEN test.equals(family, 'aurora-postgresql11'); @@ -82,29 +81,25 @@ export = { }, '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' }, - ]); + const postgres = DatabaseClusterEngine.AURORA_POSTGRESQL; - // 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'); + // the PostgreSQL engine knows about the following major versions: 9.6, 10 and 11 + + test.throws(() => { + postgres.withVersion('8'); + }, /No parameter group family found for database engine aurora-postgresql with version 8/); + + test.throws(() => { + postgres.withVersion('9'); + }, /No parameter group family found for database engine aurora-postgresql with version 9/); + + test.throws(() => { + postgres.withVersion('9.7'); + }, /No parameter group family found for database engine aurora-postgresql with version 9\.7/); + + test.equals(postgres.withVersion('9.6').parameterGroupFamily, 'aurora-postgresql9.6'); + test.equals(postgres.withVersion('9.6.1').parameterGroupFamily, 'aurora-postgresql9.6'); + test.equals(postgres.withVersion('10.0').parameterGroupFamily, 'aurora-postgresql10'); test.done(); },