From ef9a9cf895eda81fdee69177741dd2d963b2e47e Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 2 Apr 2019 16:07:34 +0200 Subject: [PATCH 01/34] Remove unused cluster-parameter-group-ref.ts --- .../lib/cluster-parameter-group-ref.ts | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 packages/@aws-cdk/aws-rds/lib/cluster-parameter-group-ref.ts diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group-ref.ts b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group-ref.ts deleted file mode 100644 index f0b01df78b043..0000000000000 --- a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group-ref.ts +++ /dev/null @@ -1,46 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); - -/** - * A cluster parameter group - */ -export abstract class ClusterParameterGroupRef extends cdk.Construct { - /** - * Import a parameter group - */ - public static import(scope: cdk.Construct, id: string, props: ClusterParameterGroupRefProps): ClusterParameterGroupRef { - return new ImportedClusterParameterGroup(scope, id, props); - } - - /** - * Name of this parameter group - */ - public abstract readonly parameterGroupName: string; - - /** - * Export this parameter group - */ - public export(): ClusterParameterGroupRefProps { - return { - parameterGroupName: new cdk.CfnOutput(this, 'ParameterGroupName', { value: this.parameterGroupName }).makeImportValue().toString() - }; - } -} - -/** - * Properties to reference a cluster parameter group - */ -export interface ClusterParameterGroupRefProps { - parameterGroupName: string; -} - -/** - * An imported cluster parameter group - */ -class ImportedClusterParameterGroup extends ClusterParameterGroupRef { - public readonly parameterGroupName: string; - - constructor(scope: cdk.Construct, id: string, props: ClusterParameterGroupRefProps) { - super(scope, id); - this.parameterGroupName = props.parameterGroupName; - } -} From 1c17af4366c2c2b32463c4c5fd283767955ce730 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 2 Apr 2019 17:05:48 +0200 Subject: [PATCH 02/34] Refactor parameter group --- .../aws-rds/lib/cluster-parameter-group.ts | 130 ---------------- packages/@aws-cdk/aws-rds/lib/cluster.ts | 4 +- packages/@aws-cdk/aws-rds/lib/index.ts | 2 +- .../@aws-cdk/aws-rds/lib/parameter-group.ts | 140 ++++++++++++++++++ packages/@aws-cdk/aws-rds/lib/props.ts | 5 - .../@aws-cdk/aws-rds/test/integ.cluster.ts | 6 +- .../@aws-cdk/aws-rds/test/test.cluster.ts | 18 --- .../aws-rds/test/test.parameter-group.ts | 98 ++++++++++++ 8 files changed, 245 insertions(+), 158 deletions(-) delete mode 100644 packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts create mode 100644 packages/@aws-cdk/aws-rds/lib/parameter-group.ts create mode 100644 packages/@aws-cdk/aws-rds/test/test.parameter-group.ts diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts deleted file mode 100644 index 3f8338a770409..0000000000000 --- a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts +++ /dev/null @@ -1,130 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { Parameters } from './props'; -import { CfnDBClusterParameterGroup } from './rds.generated'; - -/** - * A cluster parameter group - */ -export interface IClusterParameterGroup extends cdk.IConstruct { - /** - * Name of this parameter group - */ - readonly parameterGroupName: string; - - /** - * Export this parameter group - */ - export(): ClusterParameterGroupImportProps; -} - -/** - * Properties to reference a cluster parameter group - */ -export interface ClusterParameterGroupImportProps { - readonly parameterGroupName: string; -} - -/** - * Properties for a cluster parameter group - */ -export interface ClusterParameterGroupProps { - /** - * Database family of this parameter group - */ - readonly family: string; - - /** - * Description for this parameter group - */ - readonly description: string; - - /** - * The parameters in this parameter group - */ - readonly parameters?: Parameters; -} - -/** - * Defina a cluster parameter group - */ -export class ClusterParameterGroup extends cdk.Construct implements IClusterParameterGroup { - /** - * Import a parameter group - */ - public static import(scope: cdk.Construct, id: string, props: ClusterParameterGroupImportProps): IClusterParameterGroup { - return new ImportedClusterParameterGroup(scope, id, props); - } - - public readonly parameterGroupName: string; - private readonly parameters: Parameters = {}; - - constructor(scope: cdk.Construct, id: string, props: ClusterParameterGroupProps) { - super(scope, id); - - const resource = new CfnDBClusterParameterGroup(this, 'Resource', { - description: props.description, - family: props.family, - parameters: new cdk.Token(() => this.parameters), - }); - - for (const [key, value] of Object.entries(props.parameters || {})) { - this.setParameter(key, value); - } - - this.parameterGroupName = resource.ref; - } - - /** - * Export this parameter group - */ - public export(): ClusterParameterGroupImportProps { - return { - parameterGroupName: new cdk.CfnOutput(this, 'ParameterGroupName', { value: this.parameterGroupName }).makeImportValue().toString() - }; - } - - /** - * Set a single parameter in this parameter group - */ - public setParameter(key: string, value: string | undefined) { - if (value === undefined && key in this.parameters) { - delete this.parameters[key]; - } - if (value !== undefined) { - this.parameters[key] = value; - } - } - - /** - * Remove a previously-set parameter from this parameter group - */ - public removeParameter(key: string) { - this.setParameter(key, undefined); - } - - /** - * Validate this construct - */ - protected validate(): string[] { - if (Object.keys(this.parameters).length === 0) { - return ['At least one parameter required, call setParameter().']; - } - return []; - } -} - -/** - * An imported cluster parameter group - */ -class ImportedClusterParameterGroup extends cdk.Construct implements IClusterParameterGroup { - public readonly parameterGroupName: string; - - constructor(scope: cdk.Construct, id: string, private readonly props: ClusterParameterGroupImportProps) { - super(scope, id); - this.parameterGroupName = props.parameterGroupName; - } - - public export() { - return this.props; - } -} diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 24d547b0262c1..fc797f74b9b9d 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -2,9 +2,9 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); import cdk = require('@aws-cdk/cdk'); -import { IClusterParameterGroup } from './cluster-parameter-group'; import { DatabaseClusterImportProps, Endpoint, IDatabaseCluster } from './cluster-ref'; import { DatabaseSecret } from './database-secret'; +import { IParameterGroup } from './parameter-group'; import { BackupProps, DatabaseClusterEngine, InstanceProps, Login } from './props'; import { CfnDBCluster, CfnDBInstance, CfnDBSubnetGroup } from './rds.generated'; import { DatabaseEngine, RotationSingleUser, RotationSingleUserOptions } from './rotation-single-user'; @@ -101,7 +101,7 @@ export interface DatabaseClusterProps { * * @default No parameter group */ - readonly parameterGroup?: IClusterParameterGroup; + readonly parameterGroup?: IParameterGroup; /** * The CloudFormation policy to apply when the cluster and its instances diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index 149610e4fe159..30c402b91f1a7 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -1,7 +1,7 @@ export * from './cluster'; export * from './cluster-ref'; export * from './props'; -export * from './cluster-parameter-group'; +export * from './parameter-group'; export * from './rotation-single-user'; export * from './database-secret'; diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts new file mode 100644 index 0000000000000..0e676302c474f --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -0,0 +1,140 @@ +import cdk = require('@aws-cdk/cdk'); +import { CfnDBClusterParameterGroup, CfnDBParameterGroup } from './rds.generated'; + +/** + * A parameter group + */ +export interface IParameterGroup extends cdk.IConstruct { + /** + * The name of this parameter group + */ + readonly parameterGroupName: string; + + /** + * Exports this parameter group from the stack + */ + export(): ParameterGroupImportProps; +} + +/** + * Properties for a parameter group + */ +export interface ParameterGroupProps { + /** + * Database family of this parameter group + */ + readonly family: string; + + /** + * Description for this parameter group + */ + readonly description: string; + + /** + * The parameters in this parameter group + */ + readonly parameters: { [key: string]: string }; +} + +/** + * A new cluster or instance parameter group + */ +export abstract class ParameterGroupBase extends cdk.Construct implements IParameterGroup { + /** + * Imports a parameter group + */ + public static import(scope: cdk.Construct, id: string, props: ParameterGroupImportProps): IParameterGroup { + return new ImportedParameterGroup(scope, id, props); + } + + /** + * The name of the parameter group + */ + public abstract readonly parameterGroupName: string; + + /** + * Exports this parameter group from the stack + */ + public export(): ParameterGroupImportProps { + return { + parameterGroupName: new cdk.CfnOutput(this, 'ParameterGroupName', { value: this.parameterGroupName }).makeImportValue().toString() + }; + } +} + +/** + * A parameter group + */ +export class ParameterGroup extends ParameterGroupBase { + /** + * The name of the parameter group + */ + public readonly parameterGroupName: string; + + constructor(scope: cdk.Construct, id: string, props: ParameterGroupProps) { + super(scope, id); + + const resource = new CfnDBParameterGroup(this, 'Resource', { + description: props.description, + family: props.family, + parameters: props.parameters, + }); + + this.parameterGroupName = resource.dbParameterGroupName; + } +} + +/** + * A cluster parameter group + */ +export class ClusterParameterGroup extends ParameterGroupBase { + /** + * The name of the parameter group + */ + public readonly parameterGroupName: string; + + constructor(scope: cdk.Construct, id: string, props: ParameterGroupProps) { + super(scope, id); + + const resource = new CfnDBClusterParameterGroup(this, 'Resource', { + description: props.description, + family: props.family, + parameters: props.parameters, + }); + + this.parameterGroupName = resource.dbClusterParameterGroupName; + } +} + +/** + * Construction properties for an ImportedParameterGroup + */ +export interface ParameterGroupImportProps { + /** + * The name of the parameter group + */ + readonly parameterGroupName: string; +} + +/** + * An imported cluster or instance parameter group + */ +class ImportedParameterGroup extends cdk.Construct implements IParameterGroup { + /** + * The name of the parameter group + */ + public readonly parameterGroupName: string; + + constructor(scope: cdk.Construct, id: string, private readonly props: ParameterGroupImportProps) { + super(scope, id); + + this.parameterGroupName = props.parameterGroupName; + } + + /** + * Exports this parameter group from the stack + */ + public export(): ParameterGroupImportProps { + return this.props; + } +} diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index 002039d05c8bf..0da321365033f 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -83,8 +83,3 @@ export interface Login { */ readonly kmsKey?: kms.IEncryptionKey; } - -/** - * Type for database parameters - */ -export type Parameters = {[key: string]: any}; diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index 281f22ed4e89b..fa9f671cf5291 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -2,7 +2,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); import cdk = require('@aws-cdk/cdk'); import { DatabaseCluster, DatabaseClusterEngine } from '../lib'; -import { ClusterParameterGroup } from '../lib/cluster-parameter-group'; +import { ClusterParameterGroup } from '../lib/parameter-group'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-rds-integ'); @@ -12,8 +12,10 @@ const vpc = new ec2.VpcNetwork(stack, 'VPC', { maxAZs: 2 }); const params = new ClusterParameterGroup(stack, 'Params', { family: 'aurora5.6', description: 'A nice parameter group', + parameters: { + character_set_database: 'utf8mb4' + } }); -params.setParameter('character_set_database', 'utf8mb4'); const kmsKey = new kms.EncryptionKey(stack, 'DbSecurity'); const cluster = new DatabaseCluster(stack, 'Database', { diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index ce5d39e104573..c1e3d2582887c 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -170,24 +170,6 @@ export = { test.done(); }, - 'import/export cluster parameter group'(test: Test) { - // GIVEN - const stack = testStack(); - const group = new ClusterParameterGroup(stack, 'Params', { - family: 'hello', - description: 'desc' - }); - - // WHEN - const exported = group.export(); - const imported = ClusterParameterGroup.import(stack, 'ImportParams', exported); - - // THEN - test.deepEqual(stack.node.resolve(exported), { parameterGroupName: { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' } }); - test.deepEqual(stack.node.resolve(imported.parameterGroupName), { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' }); - test.done(); - }, - 'creates a secret when master credentials are not specified'(test: Test) { // GIVEN const stack = testStack(); diff --git a/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts b/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts new file mode 100644 index 0000000000000..bc07ac699b4f1 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts @@ -0,0 +1,98 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import { ClusterParameterGroup, ParameterGroup } from '../lib'; + +export = { + 'create a parameter group'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ParameterGroup(stack, 'Params', { + family: 'hello', + description: 'desc', + parameters: { + key: 'value' + } + }); + + // THEN + expect(stack).to(haveResource('AWS::RDS::DBParameterGroup', { + Description: 'desc', + Family: 'hello', + Parameters: { + key: 'value' + } + })); + + test.done(); + }, + + 'create a cluster parameter group'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ClusterParameterGroup(stack, 'Params', { + family: 'hello', + description: 'desc', + parameters: { + key: 'value' + } + }); + + // THEN + expect(stack).to(haveResource('AWS::RDS::DBClusterParameterGroup', { + Description: 'desc', + Family: 'hello', + Parameters: { + key: 'value' + } + })); + + test.done(); + }, + + 'import/export parameter group'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const group = new ParameterGroup(stack, 'Params', { + family: 'hello', + description: 'desc', + parameters: { + key: 'value' + } + }); + + // WHEN + const exported = group.export(); + const imported = ClusterParameterGroup.import(stack, 'ImportParams', exported); + + // THEN + test.deepEqual(stack.node.resolve(exported), { parameterGroupName: { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' } }); + test.deepEqual(stack.node.resolve(imported.parameterGroupName), { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' }); + test.done(); + }, + + 'import/export cluster parameter group'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const group = new ClusterParameterGroup(stack, 'Params', { + family: 'hello', + description: 'desc', + parameters: { + key: 'value' + } + }); + + // WHEN + const exported = group.export(); + const imported = ClusterParameterGroup.import(stack, 'ImportParams', exported); + + // THEN + test.deepEqual(stack.node.resolve(exported), { parameterGroupName: { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' } }); + test.deepEqual(stack.node.resolve(imported.parameterGroupName), { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' }); + test.done(); + } +}; From e55b6fc072b7b263980c024927340287a4d1715b Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 3 Apr 2019 22:26:33 +0200 Subject: [PATCH 03/34] Extract endpoint --- packages/@aws-cdk/aws-rds/lib/cluster-ref.ts | 29 +------------------- packages/@aws-cdk/aws-rds/lib/cluster.ts | 3 +- packages/@aws-cdk/aws-rds/lib/endpoint.ts | 27 ++++++++++++++++++ packages/@aws-cdk/aws-rds/lib/index.ts | 1 + 4 files changed, 31 insertions(+), 29 deletions(-) create mode 100644 packages/@aws-cdk/aws-rds/lib/endpoint.ts diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts index 41b32ff24f5e7..32277c8059579 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts @@ -1,6 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); import cdk = require('@aws-cdk/cdk'); +import { Endpoint } from './endpoint'; /** * Create a clustered database with a given number of instances. @@ -82,31 +83,3 @@ export interface DatabaseClusterImportProps { */ readonly instanceEndpointAddresses: string[]; } - -/** - * Connection endpoint of a database cluster or instance - * - * Consists of a combination of hostname and port. - */ -export class Endpoint { - /** - * The hostname of the endpoint - */ - public readonly hostname: string; - - /** - * The port of the endpoint - */ - public readonly port: string; - - /** - * The combination of "HOSTNAME:PORT" for this endpoint - */ - public readonly socketAddress: string; - - constructor(address: string, port: string) { - this.hostname = address; - this.port = port; - this.socketAddress = `${address}:${port}`; - } -} diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index fc797f74b9b9d..07b3a63c4e12a 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -2,8 +2,9 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); import cdk = require('@aws-cdk/cdk'); -import { DatabaseClusterImportProps, Endpoint, IDatabaseCluster } from './cluster-ref'; +import { DatabaseClusterImportProps, IDatabaseCluster } from './cluster-ref'; import { DatabaseSecret } from './database-secret'; +import { Endpoint } from './endpoint'; import { IParameterGroup } from './parameter-group'; import { BackupProps, DatabaseClusterEngine, InstanceProps, Login } from './props'; import { CfnDBCluster, CfnDBInstance, CfnDBSubnetGroup } from './rds.generated'; diff --git a/packages/@aws-cdk/aws-rds/lib/endpoint.ts b/packages/@aws-cdk/aws-rds/lib/endpoint.ts new file mode 100644 index 0000000000000..793060ccc0543 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/endpoint.ts @@ -0,0 +1,27 @@ +/** + * Connection endpoint of a database cluster or instance + * + * Consists of a combination of hostname and port. + */ +export class Endpoint { + /** + * The hostname of the endpoint + */ + public readonly hostname: string; + + /** + * The port of the endpoint + */ + public readonly port: string; + + /** + * The combination of "HOSTNAME:PORT" for this endpoint + */ + public readonly socketAddress: string; + + constructor(address: string, port: string) { + this.hostname = address; + this.port = port; + this.socketAddress = `${address}:${port}`; + } +} diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index 30c402b91f1a7..d4670e9dc2961 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -4,6 +4,7 @@ export * from './props'; export * from './parameter-group'; export * from './rotation-single-user'; export * from './database-secret'; +export * from './endpoint'; // AWS::RDS CloudFormation Resources: export * from './rds.generated'; From 8c31724c09cbf67962b1c8249cc27cc4e7b428ef Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 3 Apr 2019 22:27:33 +0200 Subject: [PATCH 04/34] Add option group --- packages/@aws-cdk/aws-rds/lib/index.ts | 1 + packages/@aws-cdk/aws-rds/lib/option-group.ts | 149 ++++++++++++++++++ .../aws-rds/test/test.option-group.ts | 59 +++++++ 3 files changed, 209 insertions(+) create mode 100644 packages/@aws-cdk/aws-rds/lib/option-group.ts create mode 100644 packages/@aws-cdk/aws-rds/test/test.option-group.ts diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index d4670e9dc2961..029cf72f5d15d 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -5,6 +5,7 @@ export * from './parameter-group'; export * from './rotation-single-user'; export * from './database-secret'; export * from './endpoint'; +export * from './option-group'; // AWS::RDS CloudFormation Resources: export * from './rds.generated'; diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts new file mode 100644 index 0000000000000..6252c76eb45a4 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -0,0 +1,149 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { DatabaseInstanceEngine } from './instance'; +import { CfnOptionGroup } from './rds.generated'; + +/** + * An option group + */ +export interface IOptionGroup extends cdk.IConstruct { + /** + * The name of the option group. + */ + readonly optionGroupName: string; + + /** + * Exports this option group from the stack. + */ + export(): OptionGroupImportProps; +} + +/** + * Configuration properties for an option. + */ +export interface OptionConfiguration { + /** + * The name of the option. + */ + readonly name: string; + + /** + * The settings for the option. + * + * @default no settings + */ + readonly settings?: { [name: string]: string }; + + /** + * The version for the option. + * + * @default no version + */ + readonly version?: string; + + /** + * The port number that this option uses. + * + * @default no port + */ + readonly port?: number; + + /** + * A list of VPC security group for this option. + */ + readonly securityGroups?: ec2.ISecurityGroup[]; +} + +/** + * Construction properties for an OptionGroup. + */ +export interface OptionGroupProps { + /** + * The name of the database engine that this option group is associated with. + */ + readonly engineName: DatabaseInstanceEngine + + /** + * The major version number of the database engine that this option group + * is associated with. + */ + readonly majorEngineVersion: string; + + /** + * A description of the option group. + * + * @default a CDK generated description + */ + readonly description?: string; + + /** + * The configurations for this option group. + */ + readonly configurations: OptionConfiguration[]; +} + +export class OptionGroup extends cdk.Construct implements IOptionGroup { + /** + * Import an existing option group. + */ + public static import(scope: cdk.Construct, id: string, props: OptionGroupImportProps) { + return new ImportedOptionGroup(scope, id, props); + } + + public readonly optionGroupName: string; + + constructor(scope: cdk.Construct, id: string, props: OptionGroupProps) { + super(scope, id); + + const optionGroup = new CfnOptionGroup(this, 'Resource', { + engineName: props.engineName, + majorEngineVersion: props.majorEngineVersion, + optionGroupDescription: props.description || `Option group for ${props.engineName} ${props.majorEngineVersion}`, + optionConfigurations: renderConfigurations(props.configurations) + }); + + this.optionGroupName = optionGroup.optionGroupName; + } + + public export(): OptionGroupImportProps { + return { + optionGroupName: new cdk.CfnOutput(this, 'OptionGroupName', { value: this.optionGroupName }).makeImportValue().toString(), + }; + } +} + +export interface OptionGroupImportProps { + /** + * The name of the option group. + */ + readonly optionGroupName: string; +} + +export class ImportedOptionGroup extends cdk.Construct implements IOptionGroup { + public readonly optionGroupName: string; + + constructor(scope: cdk.Construct, id: string, private readonly props: OptionGroupImportProps) { + super(scope, id); + + this.optionGroupName = props.optionGroupName; + } + + public export() { + return this.props; + } +} + +/** + * Renders the option configurations specifications. + * + * @param configs a list of configurations + */ +function renderConfigurations(configs: OptionConfiguration[]): CfnOptionGroup.OptionConfigurationProperty[] { + return configs.map(config => ({ + optionName: config.name, + optionSettings: config.settings && Object.entries(config.settings).map(([name, value]) => ({ name, value })), + optionVersion: config.version, + port: config.port, + vpcSecurityGroupMemberships: config.securityGroups && config.securityGroups.map(s => s.securityGroupId) + })); +} diff --git a/packages/@aws-cdk/aws-rds/test/test.option-group.ts b/packages/@aws-cdk/aws-rds/test/test.option-group.ts new file mode 100644 index 0000000000000..08e322f42bff4 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/test.option-group.ts @@ -0,0 +1,59 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import { DatabaseInstanceEngine, OptionGroup } from '../lib'; + +export = { + 'create an option group'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new OptionGroup(stack, 'Params', { + engineName: DatabaseInstanceEngine.OracleSE1, + majorEngineVersion: '11.2', + configurations: [ + { + name: 'XMLDB' + } + ] + }); + + // THEN + expect(stack).to(haveResource('AWS::RDS::OptionGroup', { + EngineName: 'oracle-se1', + MajorEngineVersion: '11.2', + OptionGroupDescription: 'Option group for oracle-se1 11.2', + OptionConfigurations: [ + { + OptionName: 'XMLDB' + } + ] + })); + + test.done(); + }, + + 'import/export option group'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const group = new OptionGroup(stack, 'Params', { + engineName: DatabaseInstanceEngine.OracleSE1, + majorEngineVersion: '11.2', + configurations: [ + { + name: 'XMLDB' + } + ] + }); + + // WHEN + const exported = group.export(); + const imported = OptionGroup.import(stack, 'ImportParams', exported); + + // THEN + test.deepEqual(stack.node.resolve(exported), { optionGroupName: { 'Fn::ImportValue': 'Stack:ParamsOptionGroupNameEC112901' } }); + test.deepEqual(stack.node.resolve(imported.optionGroupName), { 'Fn::ImportValue': 'Stack:ParamsOptionGroupNameEC112901' }); + test.done(); + } +}; From cc49f71ef1c7c3dcfba7339954085325ed8be517 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 5 Apr 2019 15:33:57 +0200 Subject: [PATCH 05/34] Make parameter group description optional --- packages/@aws-cdk/aws-rds/lib/parameter-group.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts index 0e676302c474f..8112bca017382 100644 --- a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -27,8 +27,10 @@ export interface ParameterGroupProps { /** * Description for this parameter group + * + * @default a CDK generated description */ - readonly description: string; + readonly description?: string; /** * The parameters in this parameter group @@ -75,7 +77,7 @@ export class ParameterGroup extends ParameterGroupBase { super(scope, id); const resource = new CfnDBParameterGroup(this, 'Resource', { - description: props.description, + description: props.description || `Parameter group for ${props.family}`, family: props.family, parameters: props.parameters, }); @@ -97,7 +99,7 @@ export class ClusterParameterGroup extends ParameterGroupBase { super(scope, id); const resource = new CfnDBClusterParameterGroup(this, 'Resource', { - description: props.description, + description: props.description || `Cluster parameter group for ${props.family}`, family: props.family, parameters: props.parameters, }); From d798e0cec0ab3d56fe82b809d99c0206d444e5f2 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 5 Apr 2019 17:32:58 +0200 Subject: [PATCH 06/34] Add instance --- packages/@aws-cdk/aws-rds/lib/index.ts | 1 + packages/@aws-cdk/aws-rds/lib/instance.ts | 931 ++++++++++++++ packages/@aws-cdk/aws-rds/package.json | 7 +- .../test/integ.instance.lit.expected.json | 1065 +++++++++++++++++ .../aws-rds/test/integ.instance.lit.ts | 90 ++ .../@aws-cdk/aws-rds/test/test.instance.ts | 374 ++++++ .../aws-rds/test/test.rotation-single-user.ts | 167 ++- 7 files changed, 2633 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-rds/lib/instance.ts create mode 100644 packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json create mode 100644 packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts create mode 100644 packages/@aws-cdk/aws-rds/test/test.instance.ts diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index 029cf72f5d15d..7eaefcf66de92 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -6,6 +6,7 @@ export * from './rotation-single-user'; export * from './database-secret'; export * from './endpoint'; export * from './option-group'; +export * from './instance'; // AWS::RDS CloudFormation Resources: export * from './rds.generated'; diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts new file mode 100644 index 0000000000000..5598183a11456 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -0,0 +1,931 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import events = require('@aws-cdk/aws-events'); +import iam = require('@aws-cdk/aws-iam'); +import kms = require('@aws-cdk/aws-kms'); +import lambda = require('@aws-cdk/aws-lambda'); +import logs = require('@aws-cdk/aws-logs'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); +import cdk = require('@aws-cdk/cdk'); +import { DatabaseSecret } from './database-secret'; +import { Endpoint } from './endpoint'; +import { IOptionGroup} from './option-group'; +import { IParameterGroup } from './parameter-group'; +import { CfnDBInstance, CfnDBInstanceProps, CfnDBSubnetGroup } from './rds.generated'; +import { DatabaseEngine, RotationSingleUser, RotationSingleUserOptions } from './rotation-single-user'; + +export interface IDatabaseInstance extends cdk.IConstruct, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget { + /** + * The instance identifier. + */ + readonly instanceIdentifier: string; + + /** + * The instance endpoint. + */ + readonly instanceEndpoint: Endpoint; + + /** + * The security group identifier of the instance. + */ + readonly securityGroupId: string; + + /** + * Exports this instance from the stack. + */ + export(): DatabaseInstanceImportProps; + + /** + * Defines a CloudWatch event rule which triggers for instance events. Use + * `rule.addEventPattern(pattern)` to specify a filter. + */ + onEvent(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule; +} + +/** + * A new or imported database instance. + */ +export abstract class DatabaseInstanceBase extends cdk.Construct implements IDatabaseInstance { + /** + * Import an existing database instance. + */ + public static import(scope: cdk.Construct, id: string, props: DatabaseInstanceImportProps): IDatabaseInstance { + return new ImportedDatabaseInstance(scope, id, props); + } + + public abstract readonly instanceIdentifier: string; + public abstract readonly instanceEndpoint: Endpoint; + public abstract readonly connections: ec2.Connections; + public abstract readonly securityGroupId: string; + + public abstract export(): DatabaseInstanceImportProps; + + /** + * Defines a CloudWatch event rule which triggers for instance events. Use + * `rule.addEventPattern(pattern)` to specify a filter. + */ + public onEvent(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) { + const rule = new events.EventRule(this, id, options); + rule.addEventPattern({ + source: [ 'aws.rds' ], + resources: [ + this.node.stack.formatArn({ + service: 'rds', + resource: 'db', + sep: ':', + resourceName: this.instanceIdentifier + }) + ] + }); + rule.addTarget(target); + return rule; + } + + /** + * Renders the secret attachment target specifications. + */ + public asSecretAttachmentTarget(): secretsmanager.SecretAttachmentTargetProps { + return { + targetId: this.instanceIdentifier, + targetType: secretsmanager.AttachmentTargetType.Instance + }; + } +} + +/** + * The engine for the database instance. + */ +export enum DatabaseInstanceEngine { + Aurora = 'aurora', + AuroraMysql = 'aurora-mysql', + AuroraPostgresql = 'aurora-postgresql', + MariaDb = 'mariadb', + Mysql = 'mysql', + OracleEE = 'oracle-ee', + OracleSE2 = 'oracle-se2', + OracleSE1 = 'oracle-se1', + OracleSE = 'oracle-se', + Postgres = 'postgres', + SqlServerEE = 'sqlserver-ee', + SqlServerSE = 'sqlserver-se', + SqlServerEX = 'sqlserver-ex', + SqlServerWeb = 'sqlserver-web' +} + +/** + * The license model. + */ +export enum LicenseModel { + /** + * License included. + */ + LicenseIncluded = 'license-included', + + /** + * Bring your own licencse. + */ + BringYourOwnLicense = 'bring-your-own-license', + + /** + * General public license. + */ + GeneralPublicLicense = 'general-public-license' +} + +/** + * The processor features. + */ +export interface ProcessorFeatures { + /** + * The number of CPU core. + */ + readonly coreCount?: number; + + /** + * The number of threads per core. + */ + readonly threadsPerCore?: number; +} + +/** + * The type of storage. + */ +export enum StorageType { + /** + * Standard. + */ + Standard = 'standard', + + /** + * General purpose (SSD). + */ + GP2 = 'gp2', + + /** + * Provisioned IOPS (SSD). + */ + IO1 = 'io1' +} + +/** + * The retention period for Performance Insight. + */ +export enum PerformanceInsightRetentionPeriod { + /** + * Default retention period of 7 days. + */ + Default = 7, + + /** + * Long term retention period of 2 years. + */ + LongTerm = 731 +} + +/** + * 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. + * + * @default false + */ + readonly multiAz?: boolean; + + /** + * The name of the Availability Zone where the DB instance will be located. + * + * @default no preference + */ + readonly availabilityZone?: string; + + /** + * The storage type. + * + * @default GP2 + */ + readonly storageType?: StorageType; + + /** + * The number of I/O operations per second (IOPS) that the database provisions. + * The value must be equal to or greater than 1000. + * + * @default no iops + */ + readonly iops?: number; + + /** + * The number of CPU cores and the number of threads per core. + * + * @default no processor features + */ + readonly processorFeatures?: ProcessorFeatures; + + /** + * A name for the DB instance. If you specify a name, AWS CloudFormation + * converts it to lowercase. + * + * @default a CloudFormation generated name + */ + readonly instanceIdentifier?: string; + + /** + * The VPC network where the DB subnet group should be created. + */ + readonly vpc: ec2.IVpcNetwork; + + /** + * The type of subnets to add to the created DB subnet group. + * + * @default private + */ + readonly vpcPlacement?: ec2.SubnetSelection; + + /** + * The port for the instance. + */ + readonly port?: number; + + /** + * The option group to associate with the instance. + * + * @default no option group + */ + readonly optionGroup?: IOptionGroup; + + /** + * Whether to enable mapping of AWS Identity and Access Management (IAM) accounts + * to database accounts. + * + * @default false + */ + readonly enableIAMDatabaseAuthentication?: boolean; + + /** + * The number of days during which automatic DB snapshots are retained. Set + * to zero to disable backups. + * + * @default 1 day + */ + readonly backupRetentionPeriod?: number; + + /** + * The daily time range during which automated backups are performed. + * + * @default no preference + */ + readonly preferredBackupWindow?: string; + + /** + * Indicates whether to copy all of the user-defined tags from the + * DB instance to snapshots of the DB instance. + * + * @default true + */ + readonly copyTagsToSnapshot?: boolean; + + /** + * Indicates whether automated backups should be deleted or retained when + * you delete a DB instance. + * + * @default false + */ + readonly deleteAutomatedBackups?: boolean; + + /** + * The interval, in seconds, between points when Amazon RDS collects enhanced + * monitoring metrics for the DB instance. + * + * @default no enhanced monitoring + */ + readonly monitoringInterval?: number; + + /** + * Whether to enable Performance Insights for the DB instance. + * + * @default false + */ + readonly enablePerformanceInsights?: boolean; + + /** + * The amount of time, in days, to retain Performance Insights data. + * + * @default 7 days + */ + readonly performanceInsightRetentionPeriod?: PerformanceInsightRetentionPeriod; + + /** + * The AWS KMS key for encryption of Performance Insights data. + * + * @default default master key + */ + readonly performanceInsightKmsKey?: kms.IEncryptionKey; + + /** + * The list of log types that need to be enabled for exporting to + * CloudWatch Logs. + * + * @default no log exports + */ + readonly cloudwatchLogsExports?: string[]; + + /** + * The number of days log events are kept in CloudWatch Logs. When updating + * this property, unsetting it doesn't remove the log retention policy. To + * remove the retention policy, set the value to `Infinity`. + * + * @default logs never expire + */ + readonly cloudwatchLogsRetention?: logs.RetentionDays; + + /** + * Indicates that minor engine upgrades are applied automatically to the + * DB instance during the maintenance window. + * + * @default true + */ + readonly autoMinorVersionUpgrade?: boolean; + + /** + * The weekly time range (in UTC) during which system maintenance can occur. + * + * @default no preference + */ + readonly preferredMaintenanceWindow?: string; + + /** + * Indicates whether the DB instance should have deletion protection enabled. + * + * @default true + */ + readonly deletionProtection?: boolean; + + /** + * The CloudFormation policy to apply when the cluster and its instances + * are removed from the stack or replaced during an update. + * + * @default Retain + */ + readonly deleteReplacePolicy?: cdk.DeletionPolicy +} + +/** + * A new database instance. + */ +export abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IDatabaseInstance { + public abstract readonly instanceIdentifier: string; + public abstract readonly instanceEndpoint: Endpoint; + public abstract readonly connections: ec2.Connections; + + public readonly securityGroupId: string; + public readonly vpc: ec2.IVpcNetwork; + + protected readonly vpcPlacement?: ec2.SubnetSelection; + protected readonly newCfnProps: CfnDBInstanceProps; + protected readonly securityGroup: ec2.SecurityGroup; + + private readonly cloudwatchLogsExports?: string[]; + private readonly cloudwatchLogsRetention?: logs.RetentionDays; + + constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceNewProps) { + super(scope, id); + + this.vpc = props.vpc; + this.vpcPlacement = props.vpcPlacement; + + const subnetIds = props.vpc.subnetIds(props.vpcPlacement); + + const subnetGroup = new CfnDBSubnetGroup(this, 'SubnetGroup', { + dbSubnetGroupDescription: `Subnet group for ${this.node.id} database`, + subnetIds + }); + + this.securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { + description: `Security group for ${this.node.id} database`, + vpc: props.vpc + }); + this.securityGroupId = this.securityGroup.securityGroupId; + + let monitoringRole; + if (props.monitoringInterval) { + monitoringRole = new iam.Role(this, 'MonitoringRole', { + assumedBy: new iam.ServicePrincipal('monitoring.rds.amazonaws.com'), + managedPolicyArns: [this.node.stack.formatArn({ + service: 'iam', + region: '', + account: 'aws', + resource: 'policy', + resourceName: 'service-role/AmazonRDSEnhancedMonitoringRole' + })] + }); + } + + const deletionProtection = props.deletionProtection !== undefined ? props.deletionProtection : true; + const storageType = props.storageType || StorageType.IO1; + const iops = storageType === StorageType.IO1 ? (props.iops || 1000) : undefined; + + this.cloudwatchLogsExports = props.cloudwatchLogsExports; + this.cloudwatchLogsRetention = props.cloudwatchLogsRetention; + + this.newCfnProps = { + autoMinorVersionUpgrade: props.autoMinorVersionUpgrade, + availabilityZone: props.multiAz ? undefined : props.availabilityZone, + backupRetentionPeriod: props.backupRetentionPeriod ? props.backupRetentionPeriod.toString() : undefined, + copyTagsToSnapshot: props.copyTagsToSnapshot !== undefined ? props.copyTagsToSnapshot : true, + dbInstanceClass: `db.${props.instanceClass}`, + dbInstanceIdentifier: props.instanceIdentifier, + dbSubnetGroupName: subnetGroup.dbSubnetGroupName, + deleteAutomatedBackups: props.deleteAutomatedBackups, + deletionProtection, + enableCloudwatchLogsExports: this.cloudwatchLogsExports, + enableIamDatabaseAuthentication: props.enableIAMDatabaseAuthentication, + enablePerformanceInsights: props.enablePerformanceInsights, + iops, + monitoringInterval: props.monitoringInterval, + monitoringRoleArn: monitoringRole && monitoringRole.roleArn, + multiAz: props.multiAz, + optionGroupName: props.optionGroup && props.optionGroup.optionGroupName, + performanceInsightsKmsKeyId: props.enablePerformanceInsights + ? props.performanceInsightKmsKey && props.performanceInsightKmsKey.keyArn + : undefined, + performanceInsightsRetentionPeriod: props.enablePerformanceInsights + ? (props.performanceInsightRetentionPeriod || PerformanceInsightRetentionPeriod.Default) + : undefined, + port: props.port ? props.port.toString() : undefined, + preferredBackupWindow: props.preferredBackupWindow, + preferredMaintenanceWindow: props.preferredMaintenanceWindow, + processorFeatures: props.processorFeatures && renderProcessorFeatures(props.processorFeatures), + publiclyAccessible: props.vpcPlacement && props.vpcPlacement.subnetType === ec2.SubnetType.Public, + storageType, + vpcSecurityGroups: [this.securityGroupId] + }; + } + + public export(): DatabaseInstanceImportProps { + return { + instanceIdentifier: new cdk.CfnOutput(this, 'InstanceId', { value: this.instanceIdentifier }).makeImportValue().toString(), + endpointAddress: new cdk.CfnOutput(this, 'EndpointAddress', { value: this.instanceEndpoint.hostname }).makeImportValue().toString(), + port: new cdk.CfnOutput(this, 'Port', { value: this.instanceEndpoint.port }).makeImportValue().toString(), + securityGroupId: new cdk.CfnOutput(this, 'SecurityGroupId', { value: this.securityGroupId, }).makeImportValue().toString() + }; + } + + protected setLogRetention() { + if (this.cloudwatchLogsExports && this.cloudwatchLogsRetention) { + for (const log of this.cloudwatchLogsExports) { + new lambda.LogRetention(this, `LogRetention${log}`, { + logGroupName: `/aws/rds/instance/${this.instanceIdentifier}/${log}`, + retentionDays: this.cloudwatchLogsRetention + }); + } + } + } +} + +/** + * Construction properties for a DatabaseInstanceSource + */ +export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { + /** + * The database engine. + */ + readonly engine: DatabaseInstanceEngine; + + /** + * The license model. + * + * @default RDS default license model + */ + readonly licenseModel?: LicenseModel; + + /** + * The engine version. To prevent automatic upgrades, be sure to specify the + * full version number. + * + * @default RDS default engine version + */ + readonly engineVersion?: string; + + /** + * Whether to allow major version upgrades. + * + * @default false + */ + readonly allowMajorVersionUpgrade?: boolean; + + /** + * The time zone of the instance. + * + * @default RDS default timezone + */ + readonly timezone?: string; + + /** + * The allocated storage size, specified in gigabytes (GB). + * + * @default 100 + */ + readonly allocatedStorage?: number; + + /** + * The master user password. + * + * @default a Secrets Manager generated password + */ + readonly masterUserPassword?: string; + + /** + * The KMS key to use to encrypt the secret for the master user password. + * + * @default default master key + */ + readonly secretKmsKey?: kms.IEncryptionKey; + + /** + * The name of the database. + * + * @default no name + */ + readonly databaseName?: string; + + /** + * The DB parameter group to associate with the instance. + * + * @default no parameter group + */ + readonly parameterGroup?: IParameterGroup; +} + +/** + * A new source database instance (not a read replica) + */ +export abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDatabaseInstance { + public abstract readonly instanceIdentifier: string; + public abstract readonly instanceEndpoint: Endpoint; + public abstract readonly connections: ec2.Connections; + public abstract readonly secret?: secretsmanager.ISecret; + + public readonly engine: DatabaseInstanceEngine; + + protected readonly sourceCfnProps: CfnDBInstanceProps; + + constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceSourceProps) { + super(scope, id, props); + + this.engine = props.engine; + + this.sourceCfnProps = { + ...this.newCfnProps, + allocatedStorage: props.allocatedStorage ? props.allocatedStorage.toString() : '100', + allowMajorVersionUpgrade: props.allowMajorVersionUpgrade, + dbName: props.databaseName, + dbParameterGroupName: props.parameterGroup && props.parameterGroup.parameterGroupName, + engine: props.engine, + engineVersion: props.engineVersion, + licenseModel: props.licenseModel, + masterUserPassword: props.masterUserPassword, + timezone: props.timezone + }; + } + + /** + * Adds the single user rotation of the master password to this instance. + */ + public addRotationSingleUser(id: string, options: RotationSingleUserOptions = {}): RotationSingleUser { + if (!this.secret) { + throw new Error('Cannot add single user rotation for an instance without secret.'); + } + return new RotationSingleUser(this, id, { + secret: this.secret, + engine: toDatabaseEngine(this.engine), + vpc: this.vpc, + vpcSubnets: this.vpcPlacement, + target: this, + ...options + }); + } +} + +/** + * Construction properties for a DatabaseInstance. + */ +export interface DatabaseInstanceProps extends DatabaseInstanceSourceProps { + /** + * The master user name. + */ + readonly masterUsername: string; + + /** + * For supported engines, specifies the character set to associate with the + * DB instance. + * + * @default RDS default character set name + */ + readonly characterSetName?: string; + + /** + * Indicates whether the DB instance is encrypted. + * + * @default false + */ + readonly storageEncrypted?: boolean; + + /** + * The master key that's used to encrypt the DB instance. + * + * @default default master key + */ + readonly kmsKey?: kms.IEncryptionKey; +} + +/** + * A database instance + */ +export class DatabaseInstance extends DatabaseInstanceSource implements IDatabaseInstance { + public readonly instanceIdentifier: string; + public readonly instanceEndpoint: Endpoint; + public readonly connections: ec2.Connections; + public readonly secret?: secretsmanager.ISecret; + + constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceProps) { + super(scope, id, props); + + let secret; + if (!props.masterUserPassword) { + secret = new DatabaseSecret(this, 'Secret', { + username: props.masterUsername, + encryptionKey: props.secretKmsKey, + }); + } + + const instance = new CfnDBInstance(this, 'Resource', { + ...this.sourceCfnProps, + characterSetName: props.characterSetName, + kmsKeyId: props.kmsKey && props.kmsKey.keyArn, + masterUsername: secret ? secret.jsonFieldValue('username') : props.masterUsername, + masterUserPassword: secret ? secret.jsonFieldValue('password') : props.masterUserPassword, + storageEncrypted: props.kmsKey ? true : props.storageEncrypted + }); + + this.instanceIdentifier = instance.dbInstanceId; + this.instanceEndpoint = new Endpoint(instance.dbInstanceEndpointAddress, instance.dbInstanceEndpointPort); + + const deleteReplacePolicy = props.deleteReplacePolicy || cdk.DeletionPolicy.Retain; + instance.options.deletionPolicy = deleteReplacePolicy; + instance.options.updateReplacePolicy = deleteReplacePolicy; + + if (secret) { + this.secret = secret.addTargetAttachment('AttachedSecret', { + target: this + }); + } + + this.connections = new ec2.Connections({ + securityGroups: [this.securityGroup], + defaultPortRange: new ec2.TcpPortFromAttribute(instance.dbInstanceEndpointPort) + }); + + this.setLogRetention(); + } +} + +/** + * Construction properties for a DatabaseInstanceFromSnapshot. + */ +export interface DatabaseInstanceFromSnapshotProps extends DatabaseInstanceSourceProps { + /** + * The name or Amazon Resource Name (ARN) of the DB snapshot that's used to + * restore the DB instance. If you're restoring from a shared manual DB + * snapshot, you must specify the ARN of the snapshot. + */ + readonly snapshotIdentifier: string; + + /** + * The master user name. + * + * @default inherited from the snapshot + */ + readonly masterUsername?: string; + + /** + * Whether to generate a new master user password and store it in + * Secrets Manager. `masterUsername` must be specified when this property + * is set to true. + * + * @default false + */ + readonly generateMasterUserPassword?: boolean; +} + +/** + * A database instance restored from a snapshot. + */ +export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource implements IDatabaseInstance { + public readonly instanceIdentifier: string; + public readonly instanceEndpoint: Endpoint; + public readonly connections: ec2.Connections; + public readonly secret?: secretsmanager.ISecret; + + constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceFromSnapshotProps) { + super(scope, id, props); + + if (props.generateMasterUserPassword && !props.masterUsername) { + throw new Error('`masterUsername` must be specified when `generateMasterUserPassword` is set to true.'); + } + + let secret; + if (!props.masterUserPassword && props.generateMasterUserPassword && props.masterUsername) { + secret = new DatabaseSecret(this, 'Secret', { + username: props.masterUsername, + encryptionKey: props.secretKmsKey, + }); + } + + const instance = new CfnDBInstance(this, 'Resource', { + ...this.sourceCfnProps, + dbSnapshotIdentifier: props.snapshotIdentifier, + masterUsername: secret ? secret.jsonFieldValue('username') : props.masterUsername, + masterUserPassword: secret ? secret.jsonFieldValue('password') : props.masterUserPassword, + }); + + this.instanceIdentifier = instance.dbInstanceId; + this.instanceEndpoint = new Endpoint(instance.dbInstanceEndpointAddress, instance.dbInstanceEndpointPort); + + const deleteReplacePolicy = props.deleteReplacePolicy || cdk.DeletionPolicy.Retain; + instance.options.deletionPolicy = deleteReplacePolicy; + instance.options.updateReplacePolicy = deleteReplacePolicy; + + if (secret) { + this.secret = secret.addTargetAttachment('AttachedSecret', { + target: this + }); + } + + this.connections = new ec2.Connections({ + securityGroups: [this.securityGroup], + defaultPortRange: new ec2.TcpPortFromAttribute(instance.dbInstanceEndpointPort) + }); + + this.setLogRetention(); + } +} + +/** + * Construction properties for a DatabaseInstanceReadReplica. + */ +export interface DatabaseInstanceReadReplicaProps extends DatabaseInstanceSourceProps { + /** + * The source database instance. + * + * Each DB instance can have a limited number of read replicas. For more + * information, see https://docs.aws.amazon.com/AmazonRDS/latest/DeveloperGuide/USER_ReadRepl.html. + * + */ + readonly sourceDatabaseInstance: IDatabaseInstance; + + /** + * Indicates whether the DB instance is encrypted. + * + * @default false + */ + readonly storageEncrypted?: boolean; + + /** + * The master key that's used to encrypt the DB instance. + * + * @default default master key + */ + readonly kmsKey?: kms.IEncryptionKey; +} + +/** + * A read replica database instance. + */ +export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements IDatabaseInstance { + public readonly instanceIdentifier: string; + public readonly instanceEndpoint: Endpoint; + public readonly connections: ec2.Connections; + + constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceReadReplicaProps) { + super(scope, id, props); + + const instance = new CfnDBInstance(this, 'Resource', { + ...this.newCfnProps, + sourceDbInstanceIdentifier: props.sourceDatabaseInstance.instanceIdentifier, + kmsKeyId: props.kmsKey && props.kmsKey.keyArn, + storageEncrypted: props.kmsKey ? true : props.storageEncrypted, + }); + + this.instanceIdentifier = instance.dbInstanceId; + this.instanceEndpoint = new Endpoint(instance.dbInstanceEndpointAddress, instance.dbInstanceEndpointPort); + + const deleteReplacePolicy = props.deleteReplacePolicy || cdk.DeletionPolicy.Retain; + instance.options.deletionPolicy = deleteReplacePolicy; + instance.options.updateReplacePolicy = deleteReplacePolicy; + + this.connections = new ec2.Connections({ + securityGroups: [this.securityGroup], + defaultPortRange: new ec2.TcpPortFromAttribute(instance.dbInstanceEndpointPort) + }); + + this.setLogRetention(); + } +} + +/** + * Construction properties for an ImportedDatabaseInstance. + */ +export interface DatabaseInstanceImportProps { + /** + * The instance identifier. + */ + readonly instanceIdentifier: string; + + /** + * The endpoint address. + */ + readonly endpointAddress: string; + + /** + * The database port. + */ + readonly port: string; + + /** + * The security group identifier of the instance. + */ + readonly securityGroupId: string; +} + +/** + * An imported database instance. + */ +class ImportedDatabaseInstance extends DatabaseInstanceBase implements IDatabaseInstance { + public readonly instanceIdentifier: string; + public readonly instanceEndpoint: Endpoint; + public readonly securityGroupId: string; + public readonly connections: ec2.Connections; + + constructor(scope: cdk.Construct, id: string, private readonly props: DatabaseInstanceImportProps) { + super(scope, id); + + this.instanceIdentifier = props.instanceIdentifier; + this.instanceEndpoint = new Endpoint(props.endpointAddress, props.port); + this.securityGroupId = props.securityGroupId; + this.connections = new ec2.Connections({ + securityGroups: [ec2.SecurityGroup.import(this, 'SecurityGroup', { securityGroupId: props.securityGroupId })], + defaultPortRange: new ec2.TcpPortFromAttribute(props.port) + }); + } + + public export() { + return this.props; + } +} + +/** + * Renders the processor features specifications + * + * @param features the processor features + */ +function renderProcessorFeatures(features: ProcessorFeatures): CfnDBInstance.ProcessorFeatureProperty[] | undefined { + const featuresList = Object.entries(features).map(([name, value]) => ({ name, value: value.toString() })); + + return featuresList.length === 0 ? undefined : featuresList; +} + +/** + * Transforms a DatbaseInstanceEngine to a DatabaseEngine. + * + * @param engine the engine to transform + */ +function toDatabaseEngine(engine: DatabaseInstanceEngine): DatabaseEngine { + switch (engine) { + case DatabaseInstanceEngine.Aurora: + case DatabaseInstanceEngine.AuroraMysql: + case DatabaseInstanceEngine.Mysql: + return DatabaseEngine.Mysql; + case DatabaseInstanceEngine.AuroraPostgresql: + case DatabaseInstanceEngine.Postgres: + return DatabaseEngine.Postgres; + case DatabaseInstanceEngine.MariaDb: + return DatabaseEngine.MariaDb; + case DatabaseInstanceEngine.OracleEE: + case DatabaseInstanceEngine.OracleSE2: + case DatabaseInstanceEngine.OracleSE1: + case DatabaseInstanceEngine.OracleSE: + return DatabaseEngine.Oracle; + case DatabaseInstanceEngine.SqlServerEE: + case DatabaseInstanceEngine.SqlServerSE: + case DatabaseInstanceEngine.SqlServerEX: + case DatabaseInstanceEngine.SqlServerWeb: + return DatabaseEngine.SqlServer; + default: + throw new Error('Unknown engine'); + } +} diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index 2a626f198c1fd..517ccffe04dbb 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -66,9 +66,11 @@ }, "dependencies": { "@aws-cdk/aws-ec2": "^0.27.0", + "@aws-cdk/aws-events": "^0.27.0", "@aws-cdk/aws-iam": "^0.27.0", "@aws-cdk/aws-kms": "^0.27.0", "@aws-cdk/aws-lambda": "^0.27.0", + "@aws-cdk/aws-logs": "^0.27.0", "@aws-cdk/aws-sam": "^0.27.0", "@aws-cdk/aws-secretsmanager": "^0.27.0", "@aws-cdk/cdk": "^0.27.0" @@ -76,11 +78,14 @@ "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { "@aws-cdk/aws-ec2": "^0.27.0", + "@aws-cdk/aws-events": "^0.27.0", "@aws-cdk/aws-kms": "^0.27.0", + "@aws-cdk/aws-logs": "^0.27.0", "@aws-cdk/aws-secretsmanager": "^0.27.0", "@aws-cdk/cdk": "^0.27.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} + 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 new file mode 100644 index 0000000000000..d3fea8e1ab451 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json @@ -0,0 +1,1065 @@ +{ + "Transform": "AWS::Serverless-2016-10-31", + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "ParameterGroup5E32DECB": { + "Type": "AWS::RDS::DBParameterGroup", + "Properties": { + "Description": "Oracle parameter group", + "Family": "oracle-se1-11.2", + "Parameters": { + "open_cursors": "2500" + } + } + }, + "OptionGroupACA43DC1": { + "Type": "AWS::RDS::OptionGroup", + "Properties": { + "EngineName": "oracle-se1", + "MajorEngineVersion": "11.2", + "OptionConfigurations": [ + { + "OptionName": "XMLDB" + } + ], + "OptionGroupDescription": "Option group for oracle-se1 11.2" + } + }, + "InstanceSubnetGroupF2CBA54F": { + "Type": "AWS::RDS::DBSubnetGroup", + "Properties": { + "DBSubnetGroupDescription": "Subnet group for Instance database", + "SubnetIds": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + } + }, + "InstanceSecurityGroupB4E5FA83": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Security group for Instance database", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "InstanceSecurityGroupfrom00000IndirectPort7D6BC055": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "CidrIp": "0.0.0.0/0", + "Description": "from 0.0.0.0/0:{IndirectPort}", + "FromPort": { + "Fn::GetAtt": [ + "InstanceC1063A87", + "Endpoint.Port" + ] + }, + "GroupId": { + "Fn::GetAtt": [ + "InstanceSecurityGroupB4E5FA83", + "GroupId" + ] + }, + "ToPort": { + "Fn::GetAtt": [ + "InstanceC1063A87", + "Endpoint.Port" + ] + } + } + }, + "InstanceSecurityGroupfromawscdkrdsinstanceInstanceRotationSecurityGroupBB71D98EIndirectPort60E4E51A": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from awscdkrdsinstanceInstanceRotationSecurityGroupBB71D98E:{IndirectPort}", + "FromPort": { + "Fn::GetAtt": [ + "InstanceC1063A87", + "Endpoint.Port" + ] + }, + "GroupId": { + "Fn::GetAtt": [ + "InstanceSecurityGroupB4E5FA83", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "InstanceRotationSecurityGroupEF8D211E", + "GroupId" + ] + }, + "ToPort": { + "Fn::GetAtt": [ + "InstanceC1063A87", + "Endpoint.Port" + ] + } + } + }, + "InstanceMonitoringRole3E2B4286": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "monitoring.rds.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole" + ] + ] + } + ] + } + }, + "InstanceSecret478E0A47": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": { + "ExcludeCharacters": "\"@/\\", + "GenerateStringKey": "password", + "PasswordLength": 30, + "SecretStringTemplate": "{\"username\":\"syscdk\"}" + } + } + }, + "InstanceSecretAttachedSecretBACA1D43": { + "Type": "AWS::SecretsManager::SecretTargetAttachment", + "Properties": { + "SecretId": { + "Ref": "InstanceSecret478E0A47" + }, + "TargetId": { + "Ref": "InstanceC1063A87" + }, + "TargetType": "AWS::RDS::DBInstance" + } + }, + "InstanceSecretAttachedSecretRotationSchedule275109B7": { + "Type": "AWS::SecretsManager::RotationSchedule", + "Properties": { + "SecretId": { + "Ref": "InstanceSecretAttachedSecretBACA1D43" + }, + "RotationLambdaARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":lambda:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":function:awscdkrdsinstanceInstanceRotation0925DC60" + ] + ] + }, + "RotationRules": { + "AutomaticallyAfterDays": 30 + } + }, + "DependsOn": [ + "InstanceRotationPermission63844D0A" + ] + }, + "InstanceC1063A87": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBInstanceClass": "db.t2.medium", + "AllocatedStorage": "100", + "AutoMinorVersionUpgrade": false, + "BackupRetentionPeriod": "7", + "CopyTagsToSnapshot": true, + "DBName": "ORCL", + "DBParameterGroupName": { + "Ref": "ParameterGroup5E32DECB" + }, + "DBSubnetGroupName": { + "Ref": "InstanceSubnetGroupF2CBA54F" + }, + "DeletionProtection": true, + "EnableCloudwatchLogsExports": [ + "trace", + "audit", + "alert", + "listener" + ], + "EnablePerformanceInsights": true, + "Engine": "oracle-se1", + "Iops": 1000, + "LicenseModel": "bring-your-own-license", + "MasterUsername": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "InstanceSecret478E0A47" + }, + ":SecretString:username::}}" + ] + ] + }, + "MasterUserPassword": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "InstanceSecret478E0A47" + }, + ":SecretString:password::}}" + ] + ] + }, + "MonitoringInterval": 60, + "MonitoringRoleArn": { + "Fn::GetAtt": [ + "InstanceMonitoringRole3E2B4286", + "Arn" + ] + }, + "MultiAZ": true, + "OptionGroupName": { + "Ref": "OptionGroupACA43DC1" + }, + "PerformanceInsightsRetentionPeriod": 7, + "StorageEncrypted": true, + "StorageType": "io1", + "VPCSecurityGroups": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroupB4E5FA83", + "GroupId" + ] + } + ] + }, + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain" + }, + "InstanceLogRetentiontrace487771C8": { + "Type": "Custom::LogRetention", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/rds/instance/", + { + "Ref": "InstanceC1063A87" + }, + "/trace" + ] + ] + }, + "RetentionInDays": 30 + } + }, + "InstanceLogRetentionaudit55C07CF6": { + "Type": "Custom::LogRetention", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/rds/instance/", + { + "Ref": "InstanceC1063A87" + }, + "/audit" + ] + ] + }, + "RetentionInDays": 30 + } + }, + "InstanceLogRetentionalert2B4B024B": { + "Type": "Custom::LogRetention", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/rds/instance/", + { + "Ref": "InstanceC1063A87" + }, + "/alert" + ] + ] + }, + "RetentionInDays": 30 + } + }, + "InstanceLogRetentionlistener232E8C3C": { + "Type": "Custom::LogRetention", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/rds/instance/", + { + "Ref": "InstanceC1063A87" + }, + "/listener" + ] + ] + }, + "RetentionInDays": 30 + } + }, + "InstanceRotationSecurityGroupEF8D211E": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-rds-instance/Instance/Rotation/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "InstanceRotationAA37A997": { + "Type": "AWS::Serverless::Application", + "Properties": { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSOracleRotationSingleUser", + "SemanticVersion": "1.0.45" + }, + "Parameters": { + "endpoint": { + "Fn::Join": [ + "", + [ + "https://secretsmanager.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + }, + "functionName": "awscdkrdsinstanceInstanceRotation0925DC60", + "vpcSecurityGroupIds": { + "Fn::GetAtt": [ + "InstanceRotationSecurityGroupEF8D211E", + "GroupId" + ] + }, + "vpcSubnetIds": { + "Fn::Join": [ + "", + [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + ",", + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + ] + } + } + } + }, + "InstanceRotationPermission63844D0A": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": "awscdkrdsinstanceInstanceRotation0925DC60", + "Principal": { + "Fn::Join": [ + "", + [ + "secretsmanager.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + }, + "DependsOn": [ + "InstanceRotationAA37A997" + ] + }, + "InstanceAvailabilityAD5D452C": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "source": [ + "aws.rds" + ], + "resources": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":db:", + { + "Ref": "InstanceC1063A87" + } + ] + ] + } + ], + "detail": { + "EventCategories": [ + "availability" + ] + } + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + "Id": "Function" + } + ] + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:PutRetentionPolicy", + "logs:DeleteRetentionPolicy" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "Roles": [ + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + } + ] + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3BucketB81211B5" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3VersionKey10C1B354" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3VersionKey10C1B354" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB", + "Arn" + ] + }, + "Runtime": "nodejs8.10" + }, + "DependsOn": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + ] + }, + "FunctionServiceRole675BB04A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "Function76856677": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = (event) => console.log(event);" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionServiceRole675BB04A", + "Arn" + ] + }, + "Runtime": "nodejs8.10" + }, + "DependsOn": [ + "FunctionServiceRole675BB04A" + ] + }, + "FunctionAllowEventRuleawscdkrdsinstanceInstanceAvailabilityCE39A6A71E819C19": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "InstanceAvailabilityAD5D452C", + "Arn" + ] + } + } + } + }, + "Parameters": { + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3BucketB81211B5": { + "Type": "String", + "Description": "S3 bucket for asset \"aws-cdk-rds-instance/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code\"" + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3VersionKey10C1B354": { + "Type": "String", + "Description": "S3 key for asset version \"aws-cdk-rds-instance/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts new file mode 100644 index 0000000000000..cd3d2914cd40b --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -0,0 +1,90 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import lambda = require('@aws-cdk/aws-lambda'); +import logs = require('@aws-cdk/aws-logs'); +import cdk = require('@aws-cdk/cdk'); +import rds = require('../lib'); + +const app = new cdk.App(); + +class DatabaseInstanceStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const vpc = new ec2.VpcNetwork(this, 'VPC', { maxAZs: 2 }); + + /// !show + // Set open cursors with parameter group + const parameterGroup = new rds.ParameterGroup(this, 'ParameterGroup', { + family: 'oracle-se1-11.2', + description: 'Oracle parameter group', + parameters: { + open_cursors: '2500' + } + }); + + /// Add XMLDB with option group + const optionGroup = new rds.OptionGroup(this, 'OptionGroup', { + engineName: rds.DatabaseInstanceEngine.OracleSE1, + majorEngineVersion: '11.2', + configurations: [ + { + name: 'XMLDB' + } + ] + }); + + // Database instance with production values + const instance = new rds.DatabaseInstance(this, 'Instance', { + engine: rds.DatabaseInstanceEngine.OracleSE1, + licenseModel: rds.LicenseModel.BringYourOwnLicense, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Medium), + multiAz: true, + storageType: rds.StorageType.IO1, + masterUsername: 'syscdk', + vpc, + databaseName: 'ORCL', + storageEncrypted: true, + backupRetentionPeriod: 7, + monitoringInterval: 60, + enablePerformanceInsights: true, + cloudwatchLogsExports: [ + 'trace', + 'audit', + 'alert', + 'listener' + ], + cloudwatchLogsRetention: logs.RetentionDays.OneMonth, + autoMinorVersionUpgrade: false, + optionGroup, + parameterGroup + }); + + // Allow connections on default port from any IPV4 + instance.connections.allowDefaultPortFromAnyIpv4(); + + // Rotate the master user password every 30 days + instance.addRotationSingleUser('Rotation'); + + // Trigger Lambda function on instance availability events + const availabilityRule = instance.onEvent('Availability'); + availabilityRule.addEventPattern({ + detail: { + EventCategories: [ + 'availability' + ] + } + }); + + const fn = new lambda.Function(this, 'Function', { + code: lambda.Code.inline('exports.handler = (event) => console.log(event);'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810 + }); + + availabilityRule.addTarget(fn); + /// !hide + } +} + +new DatabaseInstanceStack(app, 'aws-cdk-rds-instance'); +app.run(); diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts new file mode 100644 index 0000000000000..91e0ab6c2cd67 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -0,0 +1,374 @@ +import { countResources, expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); +import lambda = require('@aws-cdk/aws-lambda'); +import logs = require('@aws-cdk/aws-logs'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import rds = require('../lib'); + +export = { + 'create a DB instance'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // WHEN + new rds.DatabaseInstance(stack, 'Instance', { + engine: rds.DatabaseInstanceEngine.OracleSE1, + licenseModel: rds.LicenseModel.BringYourOwnLicense, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Medium), + multiAz: true, + storageType: rds.StorageType.IO1, + masterUsername: 'syscdk', + vpc, + databaseName: 'ORCL', + storageEncrypted: true, + backupRetentionPeriod: 7, + monitoringInterval: 60, + enablePerformanceInsights: true, + cloudwatchLogsExports: [ + 'trace', + 'audit', + 'alert', + 'listener' + ], + cloudwatchLogsRetention: logs.RetentionDays.OneMonth, + autoMinorVersionUpgrade: false, + }); + + // THEN + expect(stack).to(haveResource('AWS::RDS::DBInstance', { + Properties: { + DBInstanceClass: 'db.t2.medium', + AllocatedStorage: '100', + AutoMinorVersionUpgrade: false, + BackupRetentionPeriod: '7', + CopyTagsToSnapshot: true, + DBName: 'ORCL', + DBSubnetGroupName: { + Ref: 'InstanceSubnetGroupF2CBA54F' + }, + DeletionProtection: true, + EnableCloudwatchLogsExports: [ + 'trace', + 'audit', + 'alert', + 'listener' + ], + EnablePerformanceInsights: true, + Engine: 'oracle-se1', + Iops: 1000, + LicenseModel: 'bring-your-own-license', + MasterUsername: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:', + { + Ref: 'InstanceSecret478E0A47' + }, + ':SecretString:username::}}' + ] + ] + }, + MasterUserPassword: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:', + { + Ref: 'InstanceSecret478E0A47' + }, + ':SecretString:password::}}' + ] + ] + }, + MonitoringInterval: 60, + MonitoringRoleArn: { + 'Fn::GetAtt': [ + 'InstanceMonitoringRole3E2B4286', + 'Arn' + ] + }, + MultiAZ: true, + PerformanceInsightsRetentionPeriod: 7, + StorageEncrypted: true, + StorageType: 'io1', + VPCSecurityGroups: [ + { + 'Fn::GetAtt': [ + 'InstanceSecurityGroupB4E5FA83', + 'GroupId' + ] + } + ] + }, + DeletionPolicy: 'Retain', + UpdateReplacePolicy: 'Retain' + }, ResourcePart.CompleteDefinition)); + + expect(stack).to(haveResource('AWS::RDS::DBInstance', { + DeletionPolicy: 'Retain', + UpdateReplacePolicy: 'Retain' + }, ResourcePart.CompleteDefinition)); + + expect(stack).to(haveResource('AWS::RDS::DBSubnetGroup', { + DBSubnetGroupDescription: 'Subnet group for Instance database', + SubnetIds: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0' + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' + }, + { + Ref: 'VPCPrivateSubnet3Subnet3EDCD457' + } + ] + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Security group for Instance database', + })); + + expect(stack).to(haveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'monitoring.rds.amazonaws.com' + } + } + ], + Version: '2012-10-17' + }, + ManagedPolicyArns: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition' + }, + ':iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole' + ] + ] + } + ] + })); + + expect(stack).to(haveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: { + ExcludeCharacters: '\"@/\\', + GenerateStringKey: 'password', + PasswordLength: 30, + SecretStringTemplate: '{"username":"syscdk"}' + } + })); + + expect(stack).to(haveResource('AWS::SecretsManager::SecretTargetAttachment', { + SecretId: { + Ref: 'InstanceSecret478E0A47' + }, + TargetId: { + Ref: 'InstanceC1063A87' + }, + TargetType: 'AWS::RDS::DBInstance' + })); + + expect(stack).to(countResources('Custom::LogRetention', 4)); + + test.done(); + }, + + 'instance with option and parameter group'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + const optionGroup = new rds.OptionGroup(stack, 'OptionGroup', { + engineName: rds.DatabaseInstanceEngine.OracleSE1, + majorEngineVersion: '11.2', + configurations: [ + { + name: 'XMLDB' + } + ] + }); + + const parameterGroup = new rds.ParameterGroup(stack, 'ParameterGroup', { + family: 'hello', + description: 'desc', + parameters: { + key: 'value' + } + }); + + // WHEN + new rds.DatabaseInstance(stack, 'Database', { + engine: rds.DatabaseInstanceEngine.SqlServerEE, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + masterUsername: 'syscdk', + masterUserPassword: 'tooshort', + vpc, + optionGroup, + parameterGroup + }); + + expect(stack).to(haveResource('AWS::RDS::DBInstance', { + DBParameterGroupName: { + Ref: 'ParameterGroup5E32DECB' + }, + OptionGroupName: { + Ref: 'OptionGroupACA43DC1' + } + })); + + test.done(); + }, + + 'export/import'(test: Test) { + // GIVEN + const stack1 = new cdk.Stack(); + const stack2 = new cdk.Stack(); + + const instance = new rds.DatabaseInstance(stack1, 'Instance', { + engine: rds.DatabaseInstanceEngine.Mysql, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + masterUsername: 'admin', + vpc: new ec2.VpcNetwork(stack1, 'VPC'), + }); + + // WHEN + rds.DatabaseInstance.import(stack2, 'Instance', instance.export()); + + // THEN: No error + + test.done(); + }, + + 'create an instance from snapshot'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // WHEN + new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.Postgres, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large), + vpc + }); + + expect(stack).to(haveResource('AWS::RDS::DBInstance', { + DBSnapshotIdentifier: 'my-snapshot' + })); + + test.done(); + }, + + 'throws when trying to generate a new password from snapshot without username'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // THEN + test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.Mysql, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large), + vpc, + generateMasterUserPassword: true, + }), /`masterUsername`.*`generateMasterUserPassword`/); + + test.done(); + }, + + 'create a read replica'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const sourceInstance = new rds.DatabaseInstance(stack, 'Instance', { + engine: rds.DatabaseInstanceEngine.Mysql, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + masterUsername: 'admin', + vpc + }); + + // WHEN + new rds.DatabaseInstanceReadReplica(stack, 'ReadReplica', { + sourceDatabaseInstance: sourceInstance, + engine: rds.DatabaseInstanceEngine.Mysql, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large), + vpc + }); + + // THEN + expect(stack).to(haveResource('AWS::RDS::DBInstance', { + SourceDBInstanceIdentifier: { + Ref: 'InstanceC1063A87' + } + })); + + test.done(); + }, + + 'on event'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const instance = new rds.DatabaseInstance(stack, 'Instance', { + engine: rds.DatabaseInstanceEngine.Mysql, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + masterUsername: 'admin', + vpc + }); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.inline('dummy'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810 + }); + + // WHEN + instance.onEvent('InstanceEvent', fn); + + // THEN + expect(stack).to(haveResource('AWS::Events::Rule', { + EventPattern: { + source: [ + 'aws.rds' + ], + resources: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition' + }, + ':rds:', + { + Ref: 'AWS::Region' + }, + ':', + { + Ref: 'AWS::AccountId' + }, + ':db:', + { + Ref: 'InstanceC1063A87' + } + ] + ] + } + ] + } + })); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts b/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts index 078f94fb1cee8..6f9fb86f464e1 100644 --- a/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts +++ b/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts @@ -227,5 +227,170 @@ export = { }), /`target`.+default port range/); test.done(); - } + }, + + 'add a rds rotation single user to an instance'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const cluster = new rds.DatabaseInstance(stack, 'Database', { + engine: rds.DatabaseInstanceEngine.MariaDb, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + masterUsername: 'syscdk', + vpc + }); + + // WHEN + cluster.addRotationSingleUser('Rotation'); + + // THEN + expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { + "IpProtocol": "tcp", + "Description": "from DatabaseRotationSecurityGroup1C5A8031:{IndirectPort}", + "FromPort": { + "Fn::GetAtt": [ + "DatabaseB269D8BB", + "Endpoint.Port" + ] + }, + "GroupId": { + "Fn::GetAtt": [ + "DatabaseSecurityGroup5C91FDCB", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "DatabaseRotationSecurityGroup17736B63", + "GroupId" + ] + }, + "ToPort": { + "Fn::GetAtt": [ + "DatabaseB269D8BB", + "Endpoint.Port" + ] + } + })); + + expect(stack).to(haveResource('AWS::SecretsManager::RotationSchedule', { + "SecretId": { + "Ref": "DatabaseSecretAttachedSecretE6CAC445" + }, + "RotationLambdaARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":lambda:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":function:DatabaseRotation0D47EBD2" + ] + ] + }, + "RotationRules": { + "AutomaticallyAfterDays": 30 + } + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + "GroupDescription": "Database/Rotation/SecurityGroup" + })); + + expect(stack).to(haveResource('AWS::Serverless::Application', { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMariaDBRotationSingleUser", + "SemanticVersion": "1.0.46" + }, + "Parameters": { + "endpoint": { + "Fn::Join": [ + "", + [ + "https://secretsmanager.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + }, + "functionName": "DatabaseRotation0D47EBD2", + "vpcSecurityGroupIds": { + "Fn::GetAtt": [ + "DatabaseRotationSecurityGroup17736B63", + "GroupId" + ] + }, + "vpcSubnetIds": { + "Fn::Join": [ + "", + [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + ",", + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + ",", + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ] + ] + } + } + })); + + expect(stack).to(haveResource('AWS::Lambda::Permission', { + "Action": "lambda:InvokeFunction", + "FunctionName": "DatabaseRotation0D47EBD2", + "Principal": { + "Fn::Join": [ + "", + [ + "secretsmanager.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + })); + + test.done(); + }, + + 'throws when trying to add rotation to an instance without secret'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // WHEN + const instance = new rds.DatabaseInstance(stack, 'Database', { + engine: rds.DatabaseInstanceEngine.SqlServerEE, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + masterUsername: 'syscdk', + masterUserPassword: 'tooshort', + vpc + }); + + // THEN + test.throws(() => instance.addRotationSingleUser('Rotation'), /without secret/); + + test.done(); + }, }; From 6cc4e882aa38b8460633063138f66aad9c8a6649 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 5 Apr 2019 17:48:12 +0200 Subject: [PATCH 07/34] Update README --- packages/@aws-cdk/aws-rds/README.md | 62 ++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 958fe1e3f4988..d61a351833764 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -45,33 +45,85 @@ By default, the master password will be generated and stored in AWS Secrets Mana Your cluster will be empty by default. To add a default database upon construction, specify the `defaultDatabaseName` attribute. +### Starting an Instance Database +To set up a instance database, create an instance of `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.OracleSE1, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + masterUsername: 'syscdk', + vpc +}); +``` +By default, the master password will be generated and stored in AWS Secrets Manager. + +Use `DatabaseInstanceFromSnapshot` and `DatabaseInstanceReadReplica` to create an instance from snapshot or +a source database respectively: + +```ts +new DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.Postgres, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large), + vpc +}); + +new DatabaseInstanceReadReplica(stack, 'ReadReplica', { + sourceDatabaseInstance: sourceInstance, + engine: rds.DatabaseInstanceEngine.Postgres, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large), + vpc +}); +``` +Creating a "production" Oracle database instance with option and parameter groups: + +[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: + +```ts +const rule = instance.onEvent('InstanceEvent'); +rule.addTarget(lambdaFunction); +``` + ### Connecting -To control who can access the cluster, use the `.connections` attribute. RDS database have +To control who can access the cluster/instance, use the `.connections` attribute. RDS database have a default port, so you don't need to specify the port: ```ts cluster.connections.allowFromAnyIpv4('Open to the world'); ``` -The endpoints to access your database will be available as the `.clusterEndpoint` and `.readerEndpoint` +The endpoints to access your cluster database will be available as the `.clusterEndpoint` and `.readerEndpoint` attributes: ```ts const writeAddress = cluster.clusterEndpoint.socketAddress; // "HOSTNAME:PORT" ``` +For an instance database: +```ts +const address = instance.instanceEndpoint.socketAddress; // "HOSTNAME:PORT" +``` + ### Rotating master password -When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically: +When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically both for a cluster and an instance: -[example of setting up master password rotation](test/integ.cluster-rotation.lit.ts) +[example of setting up master password rotation for a cluster](test/integ.cluster-rotation.lit.ts) Rotation of the master password is also supported for an existing cluster: ```ts new RotationSingleUser(stack, 'Rotation', { secret: importedSecret, engine: DatabaseEngine.Oracle, - target: importedCluster, + target: importedCluster, // or importedInstance vpc: importedVpc, }) ``` From 2e5b6115cb816c205aba2136b749a379861bddd4 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 5 Apr 2019 18:26:07 +0200 Subject: [PATCH 08/34] Fix import for option group --- packages/@aws-cdk/aws-rds/lib/option-group.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index 6252c76eb45a4..82418b3354781 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -86,7 +86,7 @@ export class OptionGroup extends cdk.Construct implements IOptionGroup { /** * Import an existing option group. */ - public static import(scope: cdk.Construct, id: string, props: OptionGroupImportProps) { + public static import(scope: cdk.Construct, id: string, props: OptionGroupImportProps): IOptionGroup { return new ImportedOptionGroup(scope, id, props); } @@ -119,7 +119,7 @@ export interface OptionGroupImportProps { readonly optionGroupName: string; } -export class ImportedOptionGroup extends cdk.Construct implements IOptionGroup { +class ImportedOptionGroup extends cdk.Construct implements IOptionGroup { public readonly optionGroupName: string; constructor(scope: cdk.Construct, id: string, private readonly props: OptionGroupImportProps) { From ee391c858b9d5d4fe5010193bf2297355d4f5b73 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 5 Apr 2019 18:26:24 +0200 Subject: [PATCH 09/34] Use cdk.SecretValue --- packages/@aws-cdk/aws-rds/lib/instance.ts | 19 +++++++++++++------ .../@aws-cdk/aws-rds/test/test.instance.ts | 2 +- .../aws-rds/test/test.rotation-single-user.ts | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 5598183a11456..e4776400764a4 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -537,7 +537,7 @@ export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { * * @default a Secrets Manager generated password */ - readonly masterUserPassword?: string; + readonly masterUserPassword?: cdk.SecretValue; /** * The KMS key to use to encrypt the secret for the master user password. @@ -588,7 +588,6 @@ export abstract class DatabaseInstanceSource extends DatabaseInstanceNew impleme engine: props.engine, engineVersion: props.engineVersion, licenseModel: props.licenseModel, - masterUserPassword: props.masterUserPassword, timezone: props.timezone }; } @@ -667,8 +666,12 @@ export class DatabaseInstance extends DatabaseInstanceSource implements IDatabas ...this.sourceCfnProps, characterSetName: props.characterSetName, kmsKeyId: props.kmsKey && props.kmsKey.keyArn, - masterUsername: secret ? secret.jsonFieldValue('username') : props.masterUsername, - masterUserPassword: secret ? secret.jsonFieldValue('password') : props.masterUserPassword, + masterUsername: secret ? secret.secretJsonValue('username').toString() : props.masterUsername, + masterUserPassword: secret + ? secret.secretJsonValue('password').toString() + : (props.masterUserPassword + ? props.masterUserPassword.toString() + : undefined), storageEncrypted: props.kmsKey ? true : props.storageEncrypted }); @@ -749,8 +752,12 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme const instance = new CfnDBInstance(this, 'Resource', { ...this.sourceCfnProps, dbSnapshotIdentifier: props.snapshotIdentifier, - masterUsername: secret ? secret.jsonFieldValue('username') : props.masterUsername, - masterUserPassword: secret ? secret.jsonFieldValue('password') : props.masterUserPassword, + masterUsername: secret ? secret.secretJsonValue('username').toString() : props.masterUsername, + masterUserPassword: secret + ? secret.secretJsonValue('password').toString() + : (props.masterUserPassword + ? props.masterUserPassword.toString() + : undefined), }); this.instanceIdentifier = instance.dbInstanceId; diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index 91e0ab6c2cd67..558932ce3cf68 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -212,7 +212,7 @@ export = { engine: rds.DatabaseInstanceEngine.SqlServerEE, instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), masterUsername: 'syscdk', - masterUserPassword: 'tooshort', + masterUserPassword: cdk.SecretValue.plainText('tooshort'), vpc, optionGroup, parameterGroup diff --git a/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts b/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts index 2f3f1203387a8..066cf8e050132 100644 --- a/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts +++ b/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts @@ -385,7 +385,7 @@ export = { engine: rds.DatabaseInstanceEngine.SqlServerEE, instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), masterUsername: 'syscdk', - masterUserPassword: 'tooshort', + masterUserPassword: SecretValue.plainText('tooshort'), vpc }); From fe17f60ec5d3fe44f012204e0b9641ce9acc0915 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Sun, 7 Apr 2019 21:55:11 +0200 Subject: [PATCH 10/34] Add metrics --- packages/@aws-cdk/aws-rds/README.md | 10 +++++ packages/@aws-cdk/aws-rds/lib/index.ts | 2 + packages/@aws-cdk/aws-rds/package.json | 2 + .../@aws-cdk/aws-rds/test/test.instance.ts | 25 +++++++++++++ .../lib/augmentations/AWS_RDS_DBInstance.json | 37 +++++++++++++++++++ 5 files changed, 76 insertions(+) create mode 100644 packages/@aws-cdk/cfnspec/lib/augmentations/AWS_RDS_DBInstance.json diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index d61a351833764..a084081b63170 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -139,3 +139,13 @@ The `importedSecret` must be a JSON string with the following format: "port": "" } ``` + +### Metrics +Database instances expose [metrics (cloudwatch.Metric)](https://github.com/awslabs/aws-cdk/blob/master/packages/%40aws-cdk/aws-cloudwatch/README.md): +```ts +// The number of database connections in use (average over 5 minutes) +const dbConnections = instance.metricDatabaseConnections(); + +// The average amount of time taken per disk I/O operation (average over 1 minute) +const readLatency = instance.metric('ReadLatency', { statistic: 'Average', periodSec: 60 }); +``` diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index 7eaefcf66de92..7e1a4d63c0ac2 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -10,3 +10,5 @@ export * from './instance'; // AWS::RDS CloudFormation Resources: export * from './rds.generated'; + +import './rds-augmentations.generated'; diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index 250f640d92590..2ae2b15d3a546 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -65,6 +65,7 @@ "pkglint": "^0.28.0" }, "dependencies": { + "@aws-cdk/aws-cloudwatch": "^0.28.0", "@aws-cdk/aws-ec2": "^0.28.0", "@aws-cdk/aws-events": "^0.28.0", "@aws-cdk/aws-iam": "^0.28.0", @@ -77,6 +78,7 @@ }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-cloudwatch": "^0.28.0", "@aws-cdk/aws-ec2": "^0.28.0", "@aws-cdk/aws-events": "^0.28.0", "@aws-cdk/aws-kms": "^0.28.0", diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index 558932ce3cf68..2eb6202271eb0 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -369,6 +369,31 @@ export = { } })); + test.done(); + }, + + 'can use metricCPUUtilization'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // WHEN + const instance = new rds.DatabaseInstance(stack, 'Instance', { + engine: rds.DatabaseInstanceEngine.Mysql, + instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + masterUsername: 'admin', + vpc + }); + + // THEN + test.deepEqual(stack.node.resolve(instance.metricCPUUtilization()), { + dimensions: { DBInstanceIdentifier: { Ref: 'InstanceC1063A87' } }, + namespace: 'AWS/RDS', + metricName: 'CPUUtilization', + periodSec: 300, + statistic: 'Average' + }); + test.done(); } }; diff --git a/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_RDS_DBInstance.json b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_RDS_DBInstance.json new file mode 100644 index 0000000000000..d4e695ec82665 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_RDS_DBInstance.json @@ -0,0 +1,37 @@ +{ + "options": { + "classFile": "instance", + "class": "DatabaseInstanceBase", + "interface": "IDatabaseInstance" + }, + "metrics": { + "namespace": "AWS/RDS", + "dimensions": { "DBInstanceIdentifier": "this.instanceIdentifier" }, + "metrics": [ + { + "name": "CPUUtilization", + "documentation": "The percentage of CPU utilization." + }, + { + "name": "DatabaseConnections", + "documentation": "The number of database connections in use." + }, + { + "name": "FreeStorageSpace", + "documentation": "The amount of available storage space." + }, + { + "name": "FreeableMemory", + "documentation": "The amount of available random access memory." + }, + { + "name": "WriteIOPS", + "documentation": "The average number of disk read I/O operations per second." + }, + { + "name": "ReadIOPS", + "documentation": "The average number of disk write I/O operations per second." + } + ] + } +} From 2aed438db08db7e41d33cdcc16fbf49f920832a0 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 8 Apr 2019 12:05:27 +0200 Subject: [PATCH 11/34] Add connections to option groups --- packages/@aws-cdk/aws-rds/lib/option-group.ts | 73 +++++++++++---- .../aws-rds/test/test.option-group.ts | 89 +++++++++++++++++-- 2 files changed, 138 insertions(+), 24 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index 82418b3354781..1c49f2ce2168c 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -42,16 +42,18 @@ export interface OptionConfiguration { readonly version?: string; /** - * The port number that this option uses. + * The port number that this option uses. If `port` is specified then `vpc` + * must also be specified. * * @default no port */ readonly port?: number; /** - * A list of VPC security group for this option. + * The VPC where a security group should be created for this option. If `vpc` + * is specified then `port` must also be specified. */ - readonly securityGroups?: ec2.ISecurityGroup[]; + readonly vpc?: ec2.IVpcNetwork; } /** @@ -90,8 +92,16 @@ export class OptionGroup extends cdk.Construct implements IOptionGroup { return new ImportedOptionGroup(scope, id, props); } + /** + * The name of the option group. + */ public readonly optionGroupName: string; + /** + * The connections object for the options. + */ + public readonly optionConnections: { [key: string]: ec2.Connections } = {}; + constructor(scope: cdk.Construct, id: string, props: OptionGroupProps) { super(scope, id); @@ -99,7 +109,7 @@ export class OptionGroup extends cdk.Construct implements IOptionGroup { engineName: props.engineName, majorEngineVersion: props.majorEngineVersion, optionGroupDescription: props.description || `Option group for ${props.engineName} ${props.majorEngineVersion}`, - optionConfigurations: renderConfigurations(props.configurations) + optionConfigurations: this.renderConfigurations(props.configurations) }); this.optionGroupName = optionGroup.optionGroupName; @@ -110,6 +120,46 @@ export class OptionGroup extends cdk.Construct implements IOptionGroup { optionGroupName: new cdk.CfnOutput(this, 'OptionGroupName', { value: this.optionGroupName }).makeImportValue().toString(), }; } + + /** + * Renders the option configurations specifications. + */ + private renderConfigurations(configurations: OptionConfiguration[]): CfnOptionGroup.OptionConfigurationProperty[] { + const configs: CfnOptionGroup.OptionConfigurationProperty[] = []; + for (const config of configurations) { + let configuration: CfnOptionGroup.OptionConfigurationProperty = { + optionName: config.name, + optionSettings: config.settings && Object.entries(config.settings).map(([name, value]) => ({ name, value })), + optionVersion: config.version + }; + + if (config.port) { + if (!config.vpc) { + throw new Error('`port` and `vpc` must be specified together.'); + } + + const securityGroup = new ec2.SecurityGroup(this, `SecurityGroup${config.name}`, { + description: `Security group for ${config.name} option`, + vpc: config.vpc + }); + + this.optionConnections[config.name] = new ec2.Connections({ + securityGroups: [securityGroup], + defaultPortRange: new ec2.TcpPort(config.port) + }); + + configuration = { + ...configuration, + port: config.port, + vpcSecurityGroupMemberships: [securityGroup.securityGroupId] + }; + } + + configs.push(configuration); + } + + return configs; + } } export interface OptionGroupImportProps { @@ -132,18 +182,3 @@ class ImportedOptionGroup extends cdk.Construct implements IOptionGroup { return this.props; } } - -/** - * Renders the option configurations specifications. - * - * @param configs a list of configurations - */ -function renderConfigurations(configs: OptionConfiguration[]): CfnOptionGroup.OptionConfigurationProperty[] { - return configs.map(config => ({ - optionName: config.name, - optionSettings: config.settings && Object.entries(config.settings).map(([name, value]) => ({ name, value })), - optionVersion: config.version, - port: config.port, - vpcSecurityGroupMemberships: config.securityGroups && config.securityGroups.map(s => s.securityGroupId) - })); -} 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 08e322f42bff4..95230b1beefb0 100644 --- a/packages/@aws-cdk/aws-rds/test/test.option-group.ts +++ b/packages/@aws-cdk/aws-rds/test/test.option-group.ts @@ -1,4 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import { DatabaseInstanceEngine, OptionGroup } from '../lib'; @@ -9,7 +10,7 @@ export = { const stack = new cdk.Stack(); // WHEN - new OptionGroup(stack, 'Params', { + new OptionGroup(stack, 'Options', { engineName: DatabaseInstanceEngine.OracleSE1, majorEngineVersion: '11.2', configurations: [ @@ -34,10 +35,88 @@ export = { test.done(); }, + 'option group with security groups'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // WHEN + const optionGroup = new OptionGroup(stack, 'Options', { + engineName: DatabaseInstanceEngine.OracleSE1, + majorEngineVersion: '11.2', + configurations: [ + { + name: 'OEM', + port: 1158, + vpc + } + ] + }); + optionGroup.optionConnections.OEM.connections.allowDefaultPortFromAnyIpv4(); + + // THEN + expect(stack).to(haveResource('AWS::RDS::OptionGroup', { + EngineName: 'oracle-se1', + MajorEngineVersion: '11.2', + OptionGroupDescription: 'Option group for oracle-se1 11.2', + OptionConfigurations: [ + { + OptionName: 'OEM', + Port: 1158, + VpcSecurityGroupMemberships: [ + { + 'Fn::GetAtt': [ + 'OptionsSecurityGroupOEM6C9FE79D', + 'GroupId' + ] + } + ] + } + ] + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Security group for OEM option', + SecurityGroupIngress: [ + { + CidrIp: '0.0.0.0/0', + Description: 'from 0.0.0.0/0:1158', + FromPort: 1158, + IpProtocol: "tcp", + ToPort: 1158 + } + ], + VpcId: { + Ref: 'VPCB9E5F0B4' + } + })); + + test.done(); + }, + + 'throws when using an option with port and no vpc'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // THEN + test.throws(() => new OptionGroup(stack, 'Options', { + engineName: DatabaseInstanceEngine.OracleSE1, + majorEngineVersion: '11.2', + configurations: [ + { + name: 'OEM', + port: 1158 + } + ] + }), /`port`.*`vpc`/); + + test.done(); + }, + 'import/export option group'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const group = new OptionGroup(stack, 'Params', { + const group = new OptionGroup(stack, 'Options', { engineName: DatabaseInstanceEngine.OracleSE1, majorEngineVersion: '11.2', configurations: [ @@ -49,11 +128,11 @@ export = { // WHEN const exported = group.export(); - const imported = OptionGroup.import(stack, 'ImportParams', exported); + const imported = OptionGroup.import(stack, 'ImportOptions', exported); // THEN - test.deepEqual(stack.node.resolve(exported), { optionGroupName: { 'Fn::ImportValue': 'Stack:ParamsOptionGroupNameEC112901' } }); - test.deepEqual(stack.node.resolve(imported.optionGroupName), { 'Fn::ImportValue': 'Stack:ParamsOptionGroupNameEC112901' }); + test.deepEqual(stack.node.resolve(exported), { optionGroupName: { 'Fn::ImportValue': 'Stack:OptionsOptionGroupName16F3CF77' } }); + test.deepEqual(stack.node.resolve(imported.optionGroupName), { 'Fn::ImportValue': 'Stack:OptionsOptionGroupName16F3CF77' }); test.done(); } }; From 9249affaed2e29bda0a44011218dda5e7c2b1eff Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 9 Apr 2019 12:33:01 +0200 Subject: [PATCH 12/34] README --- packages/@aws-cdk/aws-rds/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index a084081b63170..c019bebc6b230 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -21,7 +21,7 @@ Not supported: ### Starting a Clustered Database -To set up a clustered database (like Aurora), create an instance of `DatabaseCluster`. You must +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: @@ -46,7 +46,7 @@ Your cluster will be empty by default. To add a default database upon constructi `defaultDatabaseName` attribute. ### Starting an Instance Database -To set up a instance database, create an instance of `DatabaseInstance`. You must +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: From 4150e19de36da0506cc66a2bc35f1c0b573712a7 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 9 Apr 2019 12:46:08 +0200 Subject: [PATCH 13/34] selectSubnets --- packages/@aws-cdk/aws-rds/lib/instance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index e4776400764a4..a91774209a144 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -398,7 +398,7 @@ export abstract class DatabaseInstanceNew extends DatabaseInstanceBase implement this.vpc = props.vpc; this.vpcPlacement = props.vpcPlacement; - const subnetIds = props.vpc.subnetIds(props.vpcPlacement); + const { subnetIds } = props.vpc.selectSubnets(props.vpcPlacement); const subnetGroup = new CfnDBSubnetGroup(this, 'SubnetGroup', { dbSubnetGroupDescription: `Subnet group for ${this.node.id} database`, From 8078dcf6ebf7144db255ecb7b92b6b9cec523eed Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 9 Apr 2019 12:48:31 +0200 Subject: [PATCH 14/34] Expose instanceArn --- packages/@aws-cdk/aws-rds/lib/instance.ts | 35 ++++++++++++----------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index a91774209a144..4c3da9b263f61 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -19,6 +19,11 @@ export interface IDatabaseInstance extends cdk.IConstruct, ec2.IConnectable, sec */ readonly instanceIdentifier: string; + /** + * The instance arn. + */ + readonly instanceArn: string; + /** * The instance endpoint. */ @@ -66,20 +71,25 @@ export abstract class DatabaseInstanceBase extends cdk.Construct implements IDat public onEvent(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) { const rule = new events.EventRule(this, id, options); rule.addEventPattern({ - source: [ 'aws.rds' ], - resources: [ - this.node.stack.formatArn({ - service: 'rds', - resource: 'db', - sep: ':', - resourceName: this.instanceIdentifier - }) - ] + source: ['aws.rds'], + resources: [this.instanceArn] }); rule.addTarget(target); return rule; } + /** + * The instance arn. + */ + public get instanceArn(): string { + return this.node.stack.formatArn({ + service: 'rds', + resource: 'db', + sep: ':', + resourceName: this.instanceIdentifier + }); + } + /** * Renders the secret attachment target specifications. */ @@ -378,10 +388,6 @@ export interface DatabaseInstanceNewProps { * A new database instance. */ export abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IDatabaseInstance { - public abstract readonly instanceIdentifier: string; - public abstract readonly instanceEndpoint: Endpoint; - public abstract readonly connections: ec2.Connections; - public readonly securityGroupId: string; public readonly vpc: ec2.IVpcNetwork; @@ -565,9 +571,6 @@ export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { * A new source database instance (not a read replica) */ export abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDatabaseInstance { - public abstract readonly instanceIdentifier: string; - public abstract readonly instanceEndpoint: Endpoint; - public abstract readonly connections: ec2.Connections; public abstract readonly secret?: secretsmanager.ISecret; public readonly engine: DatabaseInstanceEngine; From b91e3a3f9299705958c95dad444e20dd88f124e6 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 9 Apr 2019 12:56:33 +0200 Subject: [PATCH 15/34] Props and defaults --- packages/@aws-cdk/aws-rds/lib/instance.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 4c3da9b263f61..01ba1f1cd9f4b 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -225,14 +225,17 @@ export interface DatabaseInstanceNewProps { * The number of I/O operations per second (IOPS) that the database provisions. * The value must be equal to or greater than 1000. * - * @default no iops + * @default no provisioned iops */ readonly iops?: number; /** * The number of CPU cores and the number of threads per core. * - * @default no processor features + * @default the default number of CPU cores and threads per core for the + * chosen instance class. + * + * See https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.DBInstanceClass.html#USER_ConfigureProcessor */ readonly processorFeatures?: ProcessorFeatures; @@ -274,7 +277,7 @@ export interface DatabaseInstanceNewProps { * * @default false */ - readonly enableIAMDatabaseAuthentication?: boolean; + readonly iamAuthentication?: boolean; /** * The number of days during which automatic DB snapshots are retained. Set @@ -287,7 +290,7 @@ export interface DatabaseInstanceNewProps { /** * The daily time range during which automated backups are performed. * - * @default no preference + * @default an AWS RDS chosen time range */ readonly preferredBackupWindow?: string; @@ -364,7 +367,7 @@ export interface DatabaseInstanceNewProps { /** * The weekly time range (in UTC) during which system maintenance can occur. * - * @default no preference + * @default an AWS RDS chosen time range */ readonly preferredMaintenanceWindow?: string; @@ -449,7 +452,7 @@ export abstract class DatabaseInstanceNew extends DatabaseInstanceBase implement deleteAutomatedBackups: props.deleteAutomatedBackups, deletionProtection, enableCloudwatchLogsExports: this.cloudwatchLogsExports, - enableIamDatabaseAuthentication: props.enableIAMDatabaseAuthentication, + enableIamDatabaseAuthentication: props.iamAuthentication, enablePerformanceInsights: props.enablePerformanceInsights, iops, monitoringInterval: props.monitoringInterval, From 737bcaa767318663dff7ef724b4e63a3a248c5ce Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 9 Apr 2019 22:18:33 +0200 Subject: [PATCH 16/34] JSDoc --- packages/@aws-cdk/aws-rds/lib/instance.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 01ba1f1cd9f4b..5255b6185fe8e 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -261,6 +261,8 @@ export interface DatabaseInstanceNewProps { /** * The port for the instance. + * + * @default the default port for the chosen engine. */ readonly port?: number; @@ -379,8 +381,8 @@ export interface DatabaseInstanceNewProps { readonly deletionProtection?: boolean; /** - * The CloudFormation policy to apply when the cluster and its instances - * are removed from the stack or replaced during an update. + * The CloudFormation policy to apply when the instance is removed from the + * stack or replaced during an update. * * @default Retain */ From b817d9266186322fb07554a65a99bb97ca2d521b Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 10 Apr 2019 09:30:35 +0200 Subject: [PATCH 17/34] Refactor engine and secret rotation to avoid mapping functions and allow multi user --- packages/@aws-cdk/aws-rds/lib/cluster.ts | 33 ++---- packages/@aws-cdk/aws-rds/lib/index.ts | 2 +- packages/@aws-cdk/aws-rds/lib/instance.ts | 78 ++++--------- packages/@aws-cdk/aws-rds/lib/option-group.ts | 4 +- packages/@aws-cdk/aws-rds/lib/props.ts | 28 ++++- ...tion-single-user.ts => secret-rotation.ts} | 108 +++++------------- ...single-user.ts => test.secret-rotation.ts} | 28 +---- 7 files changed, 85 insertions(+), 196 deletions(-) rename packages/@aws-cdk/aws-rds/lib/{rotation-single-user.ts => secret-rotation.ts} (56%) rename packages/@aws-cdk/aws-rds/test/{test.rotation-single-user.ts => test.secret-rotation.ts} (92%) diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 0ba2c0ad183f5..2a085f8817b85 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -8,7 +8,7 @@ import { Endpoint } from './endpoint'; import { IParameterGroup } from './parameter-group'; import { BackupProps, DatabaseClusterEngine, InstanceProps, Login } from './props'; import { CfnDBCluster, CfnDBInstance, CfnDBSubnetGroup } from './rds.generated'; -import { DatabaseEngine, RotationSingleUser, RotationSingleUserOptions } from './rotation-single-user'; +import { SecretRotation, SecretRotationApplication, SecretRotationOptions } from './secret-rotation'; /** * Properties for a new database cluster @@ -218,7 +218,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu /** * The database engine of this cluster */ - public readonly engine: DatabaseClusterEngine; + private readonly secretRotationApplication: SecretRotationApplication; /** * The VPC where the DB subnet group is created. @@ -263,11 +263,11 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu }); } - this.engine = props.engine; + this.secretRotationApplication = props.engine.secretRotationApplication; const cluster = new CfnDBCluster(this, 'Resource', { // Basic - engine: this.engine, + engine: props.engine.engine, dbClusterIdentifier: props.clusterIdentifier, dbSubnetGroupName: subnetGroup.ref, vpcSecurityGroupIds: [this.securityGroupId], @@ -321,7 +321,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, { // Link to cluster - engine: props.engine, + engine: props.engine.engine, dbClusterIdentifier: cluster.ref, dbInstanceIdentifier: instanceIdentifier, // Instance properties @@ -349,13 +349,13 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu /** * Adds the single user rotation of the master password to this cluster. */ - public addRotationSingleUser(id: string, options: RotationSingleUserOptions = {}): RotationSingleUser { + public addRotationSingleUser(id: string, options: SecretRotationOptions = {}): SecretRotation { if (!this.secret) { throw new Error('Cannot add single user rotation for a cluster without secret.'); } - return new RotationSingleUser(this, id, { + return new SecretRotation(this, id, { secret: this.secret, - engine: toDatabaseEngine(this.engine), + application: this.secretRotationApplication, vpc: this.vpc, vpcSubnets: this.vpcSubnets, target: this, @@ -451,20 +451,3 @@ class ImportedDatabaseCluster extends DatabaseClusterBase implements IDatabaseCl return this.props; } } - -/** - * Transforms a DatbaseClusterEngine to a DatabaseEngine. - * - * @param engine the engine to transform - */ -function toDatabaseEngine(engine: DatabaseClusterEngine): DatabaseEngine { - switch (engine) { - case DatabaseClusterEngine.Aurora: - case DatabaseClusterEngine.AuroraMysql: - return DatabaseEngine.Mysql; - case DatabaseClusterEngine.AuroraPostgresql: - return DatabaseEngine.Postgres; - default: - throw new Error('Unknown engine'); - } -} diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index 7e1a4d63c0ac2..116e542651658 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -2,7 +2,7 @@ export * from './cluster'; export * from './cluster-ref'; export * from './props'; export * from './parameter-group'; -export * from './rotation-single-user'; +export * from './secret-rotation'; export * from './database-secret'; export * from './endpoint'; export * from './option-group'; diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 5255b6185fe8e..2e530b728f809 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -10,8 +10,9 @@ import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; import { IOptionGroup} from './option-group'; import { IParameterGroup } from './parameter-group'; +import { DatabaseClusterEngine } from './props'; import { CfnDBInstance, CfnDBInstanceProps, CfnDBSubnetGroup } from './rds.generated'; -import { DatabaseEngine, RotationSingleUser, RotationSingleUserOptions } from './rotation-single-user'; +import { SecretRotation, SecretRotationApplication, SecretRotationOptions } from './secret-rotation'; export interface IDatabaseInstance extends cdk.IConstruct, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget { /** @@ -102,23 +103,21 @@ export abstract class DatabaseInstanceBase extends cdk.Construct implements IDat } /** - * The engine for the database instance. + * A database instance engine. Provides mapping to DatabaseEngine used for + * secret rotation. */ -export enum DatabaseInstanceEngine { - Aurora = 'aurora', - AuroraMysql = 'aurora-mysql', - AuroraPostgresql = 'aurora-postgresql', - MariaDb = 'mariadb', - Mysql = 'mysql', - OracleEE = 'oracle-ee', - OracleSE2 = 'oracle-se2', - OracleSE1 = 'oracle-se1', - OracleSE = 'oracle-se', - Postgres = 'postgres', - SqlServerEE = 'sqlserver-ee', - SqlServerSE = 'sqlserver-se', - SqlServerEX = 'sqlserver-ex', - SqlServerWeb = 'sqlserver-web' +export class DatabaseInstanceEngine extends DatabaseClusterEngine { + public static readonly MariaDb = new DatabaseInstanceEngine('mariadb', SecretRotationApplication.MariaDbRotationSingleUser); + public static readonly Mysql = new DatabaseInstanceEngine('mysql', SecretRotationApplication.MysqlRotationSingleUser); + public static readonly OracleEE = new DatabaseInstanceEngine('oracle-ee', SecretRotationApplication.OracleRotationSingleUser); + public static readonly OracleSE2 = new DatabaseInstanceEngine('oracle-se2', SecretRotationApplication.OracleRotationSingleUser); + public static readonly OracleSE1 = new DatabaseInstanceEngine('oracle-se1', SecretRotationApplication.OracleRotationSingleUser); + public static readonly OracleSE = new DatabaseInstanceEngine('oracle-se', SecretRotationApplication.OracleRotationSingleUser); + public static readonly Postgres = new DatabaseInstanceEngine('postgres', SecretRotationApplication.PostgresRotationSingleUser); + public static readonly SqlServerEE = new DatabaseInstanceEngine('sqlserver-ee', SecretRotationApplication.SqlServerRotationSingleUser); + public static readonly SqlServerSE = new DatabaseInstanceEngine('sqlserver-se', SecretRotationApplication.SqlServerRotationSingleUser); + public static readonly SqlServerEX = new DatabaseInstanceEngine('sqlserver-ex', SecretRotationApplication.SqlServerRotationSingleUser); + public static readonly SqlServerWeb = new DatabaseInstanceEngine('sqlserver-web', SecretRotationApplication.SqlServerRotationSingleUser); } /** @@ -578,14 +577,14 @@ export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { export abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDatabaseInstance { public abstract readonly secret?: secretsmanager.ISecret; - public readonly engine: DatabaseInstanceEngine; - protected readonly sourceCfnProps: CfnDBInstanceProps; + private readonly secretRotationApplication: SecretRotationApplication; + constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceSourceProps) { super(scope, id, props); - this.engine = props.engine; + this.secretRotationApplication = props.engine.secretRotationApplication; this.sourceCfnProps = { ...this.newCfnProps, @@ -593,7 +592,7 @@ export abstract class DatabaseInstanceSource extends DatabaseInstanceNew impleme allowMajorVersionUpgrade: props.allowMajorVersionUpgrade, dbName: props.databaseName, dbParameterGroupName: props.parameterGroup && props.parameterGroup.parameterGroupName, - engine: props.engine, + engine: props.engine.engine, engineVersion: props.engineVersion, licenseModel: props.licenseModel, timezone: props.timezone @@ -603,13 +602,13 @@ export abstract class DatabaseInstanceSource extends DatabaseInstanceNew impleme /** * Adds the single user rotation of the master password to this instance. */ - public addRotationSingleUser(id: string, options: RotationSingleUserOptions = {}): RotationSingleUser { + public addRotationSingleUser(id: string, options: SecretRotationOptions = {}): SecretRotation { if (!this.secret) { throw new Error('Cannot add single user rotation for an instance without secret.'); } - return new RotationSingleUser(this, id, { + return new SecretRotation(this, id, { secret: this.secret, - engine: toDatabaseEngine(this.engine), + application: this.secretRotationApplication, vpc: this.vpc, vpcSubnets: this.vpcPlacement, target: this, @@ -913,34 +912,3 @@ function renderProcessorFeatures(features: ProcessorFeatures): CfnDBInstance.Pro return featuresList.length === 0 ? undefined : featuresList; } - -/** - * Transforms a DatbaseInstanceEngine to a DatabaseEngine. - * - * @param engine the engine to transform - */ -function toDatabaseEngine(engine: DatabaseInstanceEngine): DatabaseEngine { - switch (engine) { - case DatabaseInstanceEngine.Aurora: - case DatabaseInstanceEngine.AuroraMysql: - case DatabaseInstanceEngine.Mysql: - return DatabaseEngine.Mysql; - case DatabaseInstanceEngine.AuroraPostgresql: - case DatabaseInstanceEngine.Postgres: - return DatabaseEngine.Postgres; - case DatabaseInstanceEngine.MariaDb: - return DatabaseEngine.MariaDb; - case DatabaseInstanceEngine.OracleEE: - case DatabaseInstanceEngine.OracleSE2: - case DatabaseInstanceEngine.OracleSE1: - case DatabaseInstanceEngine.OracleSE: - return DatabaseEngine.Oracle; - case DatabaseInstanceEngine.SqlServerEE: - case DatabaseInstanceEngine.SqlServerSE: - case DatabaseInstanceEngine.SqlServerEX: - case DatabaseInstanceEngine.SqlServerWeb: - return DatabaseEngine.SqlServer; - default: - throw new Error('Unknown engine'); - } -} diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index 1c49f2ce2168c..c13ff6c304f2b 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -106,9 +106,9 @@ export class OptionGroup extends cdk.Construct implements IOptionGroup { super(scope, id); const optionGroup = new CfnOptionGroup(this, 'Resource', { - engineName: props.engineName, + engineName: props.engineName.engine, majorEngineVersion: props.majorEngineVersion, - optionGroupDescription: props.description || `Option group for ${props.engineName} ${props.majorEngineVersion}`, + optionGroupDescription: props.description || `Option group for ${props.engineName.engine} ${props.majorEngineVersion}`, optionConfigurations: this.renderConfigurations(props.configurations) }); diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index e1590bbf36ab5..c2e5048a238c4 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -1,15 +1,31 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); import { SecretValue } from '@aws-cdk/cdk'; +import { SecretRotationApplication } from './secret-rotation'; /** - * The engine for the database cluster + * A database cluster engine. Provides mapping to the serverless application + * used for secret rotation. */ -export enum DatabaseClusterEngine { - Aurora = 'aurora', - AuroraMysql = 'aurora-mysql', - AuroraPostgresql = 'aurora-postgresql', - Neptune = 'neptune' +export class DatabaseClusterEngine { + public static readonly Aurora = new DatabaseClusterEngine('aurora', SecretRotationApplication.MysqlRotationSingleUser); + public static readonly AuroraMysql = new DatabaseClusterEngine('aurora-mysql', SecretRotationApplication.MysqlRotationSingleUser); + public static readonly AuroraPostgresql = new DatabaseClusterEngine('aurora-postgresql', SecretRotationApplication.PostgresRotationSingleUser); + + /** + * The engine. + */ + public readonly engine: string; + + /** + * The database engine. + */ + public readonly secretRotationApplication: SecretRotationApplication; + + constructor(engine: string, secretRotationApplication: SecretRotationApplication) { + this.engine = engine; + this.secretRotationApplication = secretRotationApplication; + } } /** diff --git a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts similarity index 56% rename from packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts rename to packages/@aws-cdk/aws-rds/lib/secret-rotation.ts index 357e9504a0fd2..47e53dd087caf 100644 --- a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts +++ b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts @@ -5,14 +5,23 @@ import secretsmanager = require('@aws-cdk/aws-secretsmanager'); import cdk = require('@aws-cdk/cdk'); /** - * A serverless application location. + * A secret rotation serverless application. */ -export class ServerlessApplicationLocation { - public static readonly MariaDbRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSMariaDBRotationSingleUser', '1.0.46'); - public static readonly MysqlRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSMySQLRotationSingleUser', '1.0.74'); - public static readonly OracleRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSOracleRotationSingleUser', '1.0.45'); - public static readonly PostgresRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSPostgreSQLRotationSingleUser', '1.0.75'); - public static readonly SqlServerRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSSQLServerRotationSingleUser', '1.0.74'); +export class SecretRotationApplication { + public static readonly MariaDbRotationSingleUser = new SecretRotationApplication('SecretsManagerRDSMariaDBRotationSingleUser', '1.0.57'); + public static readonly MariaDBRotationMultiUser = new SecretRotationApplication('SecretsManagerRDSMariaDBRotationMultiUser', '1.0.57'); + + public static readonly MysqlRotationSingleUser = new SecretRotationApplication('SecretsManagerRDSMySQLRotationSingleUser', '1.0.85'); + public static readonly MysqlRotationMultiUser = new SecretRotationApplication('SecretsManagerRDSMySQLRotationMultiUser', '1.0.85'); + + public static readonly OracleRotationSingleUser = new SecretRotationApplication('SecretsManagerRDSOracleRotationSingleUser', '1.0.56'); + public static readonly OracleRotationMultiUser = new SecretRotationApplication('SecretsManagerRDSOracleRotationMultiUser', '1.0.56'); + + public static readonly PostgresRotationSingleUser = new SecretRotationApplication('SecretsManagerRDSPostgreSQLRotationSingleUser', '1.0.86'); + public static readonly PostgreSQLRotationMultiUser = new SecretRotationApplication('SecretsManagerRDSPostgreSQLRotationMultiUser ', '1.0.86'); + + public static readonly SqlServerRotationSingleUser = new SecretRotationApplication('SecretsManagerRDSSQLServerRotationSingleUser', '1.0.57'); + public static readonly SqlServerRotationMultiUser = new SecretRotationApplication('SecretsManagerRDSSQLServerRotationMultiUser', '1.0.57'); public readonly applicationId: string; public readonly semanticVersion: string; @@ -23,40 +32,10 @@ export class ServerlessApplicationLocation { } } -/** - * The RDS database engine - */ -export enum DatabaseEngine { - /** - * MariaDB - */ - MariaDb = 'mariadb', - - /** - * MySQL - */ - Mysql = 'mysql', - - /** - * Oracle - */ - Oracle = 'oracle', - - /** - * PostgreSQL - */ - Postgres = 'postgres', - - /** - * SQL Server - */ - SqlServer = 'sqlserver' -} - /** * Options to add single user rotation to a database instance or cluster. */ -export interface RotationSingleUserOptions { +export interface SecretRotationOptions { /** * Specifies the number of days after the previous rotation before * Secrets Manager triggers the next automatic rotation. @@ -64,19 +43,12 @@ export interface RotationSingleUserOptions { * @default 30 days */ readonly automaticallyAfterDays?: number; - - /** - * The location of the serverless application for the rotation. - * - * @default derived from the target's engine - */ - readonly serverlessApplicationLocation?: ServerlessApplicationLocation } /** - * Construction properties for a RotationSingleUser. + * Construction properties for a SecretRotation. */ -export interface RotationSingleUserProps extends RotationSingleUserOptions { +export interface SecretRotationProps extends SecretRotationOptions { /** * The secret to rotate. It must be a JSON string with the following format: * { @@ -85,7 +57,8 @@ export interface RotationSingleUserProps extends RotationSingleUserOptions { * 'username': , * 'password': , * 'dbname': , - * 'port': + * 'port': , + * 'masterarn': * } * * This is typically the case for a secret referenced from an AWS::SecretsManager::SecretTargetAttachment @@ -94,11 +67,9 @@ export interface RotationSingleUserProps extends RotationSingleUserOptions { readonly secret: secretsmanager.ISecret; /** - * The database engine. Either `serverlessApplicationLocation` or `engine` must be specified. - * - * @default no engine specified + * The serverless application for the rotation. */ - readonly engine?: DatabaseEngine; + readonly application: SecretRotationApplication; /** * The VPC where the Lambda rotation function will run. @@ -121,14 +92,10 @@ export interface RotationSingleUserProps extends RotationSingleUserOptions { /** * Single user secret rotation for a database instance or cluster. */ -export class RotationSingleUser extends cdk.Construct { - constructor(scope: cdk.Construct, id: string, props: RotationSingleUserProps) { +export class SecretRotation extends cdk.Construct { + constructor(scope: cdk.Construct, id: string, props: SecretRotationProps) { super(scope, id); - if (!props.serverlessApplicationLocation && !props.engine) { - throw new Error('Either `serverlessApplicationLocation` or `engine` must be specified.'); - } - if (!props.target.connections.defaultPortRange) { throw new Error('The `target` connections must have a default port range.'); } @@ -144,7 +111,7 @@ export class RotationSingleUser extends cdk.Construct { props.target.connections.allowDefaultPortFrom(securityGroup); const application = new serverless.CfnApplication(this, 'Resource', { - location: props.serverlessApplicationLocation || getApplicationLocation(props.engine), + location: props.application, parameters: { endpoint: `https://secretsmanager.${this.node.stack.region}.${this.node.stack.urlSuffix}`, functionName: rotationFunctionName, @@ -179,26 +146,3 @@ export class RotationSingleUser extends cdk.Construct { rotationSchedule.node.addDependency(permission); // Cannot rotate without permission } } - -/** - * Returns the location for the rotation single user application. - * - * @param engine the database engine - * @throws if the engine is not supported - */ -function getApplicationLocation(engine: string = ''): ServerlessApplicationLocation { - switch (engine) { - case DatabaseEngine.MariaDb: - return ServerlessApplicationLocation.MariaDbRotationSingleUser; - case DatabaseEngine.Mysql: - return ServerlessApplicationLocation.MysqlRotationSingleUser; - case DatabaseEngine.Oracle: - return ServerlessApplicationLocation.OracleRotationSingleUser; - case DatabaseEngine.Postgres: - return ServerlessApplicationLocation.PostgresRotationSingleUser; - case DatabaseEngine.SqlServer: - return ServerlessApplicationLocation.SqlServerRotationSingleUser; - default: - throw new Error(`Engine ${engine} not supported for single user rotation.`); - } -} diff --git a/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts b/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts similarity index 92% rename from packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts rename to packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts index 066cf8e050132..b4bf6c7098313 100644 --- a/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts +++ b/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts @@ -5,6 +5,7 @@ import cdk = require('@aws-cdk/cdk'); import { SecretValue } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import rds = require('../lib'); +import { SecretRotationApplication } from '../lib'; // tslint:disable:object-literal-key-quotes @@ -182,29 +183,6 @@ export = { test.done(); }, - 'throws when both application location and engine are not specified'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.VpcNetwork(stack, 'VPC'); - const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { - vpc, - }); - const target = new ec2.Connections({ - defaultPortRange: new ec2.TcpPort(1521), - securityGroups: [securityGroup] - }); - const secret = new secretsmanager.Secret(stack, 'Secret'); - - // THEN - test.throws(() => new rds.RotationSingleUser(stack, 'Rotation', { - secret, - vpc, - target - }), /`serverlessApplicationLocation`.+`engine`/); - - test.done(); - }, - 'throws when connections object has no default port range'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -220,9 +198,9 @@ export = { }); // THEN - test.throws(() => new rds.RotationSingleUser(stack, 'Rotation', { + test.throws(() => new rds.SecretRotation(stack, 'Rotation', { secret, - engine: rds.DatabaseEngine.Mysql, + application: SecretRotationApplication.MysqlRotationSingleUser, vpc, target }), /`target`.+default port range/); From 6dd669d2daba7d65522034c329154cd6b1612b41 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 10 Apr 2019 18:19:28 +0200 Subject: [PATCH 18/34] README --- packages/@aws-cdk/aws-rds/README.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index c019bebc6b230..986d7c81fecd2 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -1,24 +1,5 @@ ## AWS RDS Construct Library -The `aws-cdk-rds` package contains Constructs for setting up RDS instances. - -> Note: the functionality this package is currently limited, as the CDK team is -> focusing on other use cases first. If your use case is not listed below, you -> will have to use achieve it using CloudFormation resources. -> -> If you would like to help improve the state of this library, Pull Requests are -> welcome. - -Supported: - -* Clustered databases - -Not supported: - -* Instance databases -* Setting up from a snapshot - - ### Starting a Clustered Database To set up a clustered database (like Aurora), define a `DatabaseCluster`. You must From a9b8792cea15908175b7bb75c59eff89e9814655 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 10 Apr 2019 18:22:59 +0200 Subject: [PATCH 19/34] JSDoc --- packages/@aws-cdk/aws-rds/lib/props.ts | 2 +- packages/@aws-cdk/aws-rds/lib/secret-rotation.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index c2e5048a238c4..9b8222bf404e6 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -18,7 +18,7 @@ export class DatabaseClusterEngine { public readonly engine: string; /** - * The database engine. + * The secret rotation application. */ public readonly secretRotationApplication: SecretRotationApplication; diff --git a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts index 47e53dd087caf..cd7b50eaa3144 100644 --- a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts @@ -33,7 +33,7 @@ export class SecretRotationApplication { } /** - * Options to add single user rotation to a database instance or cluster. + * Options to add secret rotation to a database instance or cluster. */ export interface SecretRotationOptions { /** @@ -90,7 +90,7 @@ export interface SecretRotationProps extends SecretRotationOptions { } /** - * Single user secret rotation for a database instance or cluster. + * Secret rotation for a database instance or cluster. */ export class SecretRotation extends cdk.Construct { constructor(scope: cdk.Construct, id: string, props: SecretRotationProps) { From 34627f8737dade2d0676370ff94b0d0e07287838 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 10 Apr 2019 18:57:19 +0200 Subject: [PATCH 20/34] Fix sem ver of rotation app --- .../aws-rds/test/integ.cluster-rotation.lit.expected.json | 2 +- .../@aws-cdk/aws-rds/test/integ.instance.lit.expected.json | 2 +- packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json index 774d57074bb82..306be57a6bd12 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json @@ -724,7 +724,7 @@ "Properties": { "Location": { "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser", - "SemanticVersion": "1.0.74" + "SemanticVersion": "1.0.85" }, "Parameters": { "endpoint": { 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 d3fea8e1ab451..f882f01a6187e 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 @@ -737,7 +737,7 @@ "Properties": { "Location": { "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSOracleRotationSingleUser", - "SemanticVersion": "1.0.45" + "SemanticVersion": "1.0.56" }, "Parameters": { "endpoint": { diff --git a/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts b/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts index b4bf6c7098313..ed91b7a7af69f 100644 --- a/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts +++ b/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts @@ -94,7 +94,7 @@ export = { expect(stack).to(haveResource('AWS::Serverless::Application', { "Location": { "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser", - "SemanticVersion": "1.0.74" + "SemanticVersion": "1.0.85" }, "Parameters": { "endpoint": { @@ -288,7 +288,7 @@ export = { expect(stack).to(haveResource('AWS::Serverless::Application', { "Location": { "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMariaDBRotationSingleUser", - "SemanticVersion": "1.0.46" + "SemanticVersion": "1.0.57" }, "Parameters": { "endpoint": { From 305db7b4be17403a2cbd056dc1f605c9b5baa0ac Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 10 Apr 2019 21:58:27 +0200 Subject: [PATCH 21/34] JSDoc --- packages/@aws-cdk/aws-rds/lib/instance.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 2e530b728f809..d55cbfc43c10d 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -291,7 +291,15 @@ export interface DatabaseInstanceNewProps { /** * The daily time range during which automated backups are performed. * - * @default an AWS RDS chosen time range + * Constraints: + * - Must be in the format `hh24:mi-hh24:mi`. + * - Must be in Universal Coordinated Time (UTC). + * - Must not conflict with the preferred maintenance window. + * - Must be at least 30 minutes. + * + * @default a 30-minute window selected at random from an 8-hour block of + * time for each AWS Region. To see the time blocks available, see + * https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.Maintenance.html#AdjustingTheMaintenanceWindow */ readonly preferredBackupWindow?: string; @@ -365,11 +373,18 @@ export interface DatabaseInstanceNewProps { */ readonly autoMinorVersionUpgrade?: boolean; + // tslint:disable:max-line-length /** * The weekly time range (in UTC) during which system maintenance can occur. * - * @default an AWS RDS chosen time range + * Format: `ddd:hh24:mi-ddd:hh24:mi` + * Constraint: Minimum 30-minute window + * + * @default a 30-minute window selected at random from an 8-hour block of + * time for each AWS Region, occurring on a random day of the week. To see + * the time blocks available, see https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.Maintenance.html#AdjustingTheMaintenanceWindow */ + // tslint:enable:max-line-length readonly preferredMaintenanceWindow?: string; /** From ab5c6f35b2b4f8b7954f7c678387fe42bb1b0134 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 10 Apr 2019 21:58:42 +0200 Subject: [PATCH 22/34] Improve integ test --- .../test/integ.instance.lit.expected.json | 63 ++++++++++++++++++- .../aws-rds/test/integ.instance.lit.ts | 19 +++++- 2 files changed, 77 insertions(+), 5 deletions(-) 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 f882f01a6187e..9ae3f34a8afcb 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 @@ -347,13 +347,38 @@ "ParameterGroup5E32DECB": { "Type": "AWS::RDS::DBParameterGroup", "Properties": { - "Description": "Oracle parameter group", + "Description": "Parameter group for oracle-se1-11.2", "Family": "oracle-se1-11.2", "Parameters": { "open_cursors": "2500" } } }, + "OptionGroupSecurityGroupOEM7E39FD8C": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Security group for OEM option", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "from 0.0.0.0/0:1158", + "FromPort": 1158, + "IpProtocol": "tcp", + "ToPort": 1158 + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, "OptionGroupACA43DC1": { "Type": "AWS::RDS::OptionGroup", "Properties": { @@ -362,6 +387,18 @@ "OptionConfigurations": [ { "OptionName": "XMLDB" + }, + { + "OptionName": "OEM", + "Port": 1158, + "VpcSecurityGroupMemberships": [ + { + "Fn::GetAtt": [ + "OptionGroupSecurityGroupOEM7E39FD8C", + "GroupId" + ] + } + ] } ], "OptionGroupDescription": "Option group for oracle-se1 11.2" @@ -616,8 +653,8 @@ } ] }, - "DeletionPolicy": "Retain", - "UpdateReplacePolicy": "Retain" + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" }, "InstanceLogRetentiontrace487771C8": { "Type": "Custom::LogRetention", @@ -972,6 +1009,26 @@ "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" ] }, + "HighCPU94686517": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "Threshold": 90, + "Dimensions": [ + { + "Name": "DBInstanceIdentifier", + "Value": { + "Ref": "InstanceC1063A87" + } + } + ], + "MetricName": "CPUUtilization", + "Namespace": "AWS/RDS", + "Period": 300, + "Statistic": "Average" + } + }, "FunctionServiceRole675BB04A": { "Type": "AWS::IAM::Role", "Properties": { 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 cd3d2914cd40b..76fc23c244768 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -1,3 +1,4 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import lambda = require('@aws-cdk/aws-lambda'); import logs = require('@aws-cdk/aws-logs'); @@ -16,23 +17,30 @@ class DatabaseInstanceStack extends cdk.Stack { // Set open cursors with parameter group const parameterGroup = new rds.ParameterGroup(this, 'ParameterGroup', { family: 'oracle-se1-11.2', - description: 'Oracle parameter group', parameters: { open_cursors: '2500' } }); - /// Add XMLDB with option group + /// Add XMLDB and OEM with option group const optionGroup = new rds.OptionGroup(this, 'OptionGroup', { engineName: rds.DatabaseInstanceEngine.OracleSE1, majorEngineVersion: '11.2', configurations: [ { name: 'XMLDB' + }, + { + name: 'OEM', + port: 1158, + vpc } ] }); + // Allow connections to OEM + optionGroup.optionConnections.OEM.connections.allowDefaultPortFromAnyIpv4(); + // Database instance with production values const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.OracleSE1, @@ -65,6 +73,13 @@ class DatabaseInstanceStack extends cdk.Stack { // Rotate the master user password every 30 days instance.addRotationSingleUser('Rotation'); + // Add alarm for high CPU + new cloudwatch.Alarm(this, 'HighCPU', { + metric: instance.metricCPUUtilization(), + threshold: 90, + evaluationPeriods: 1 + }); + // Trigger Lambda function on instance availability events const availabilityRule = instance.onEvent('Availability'); availabilityRule.addEventPattern({ From 7c5526ab5d53f4e1d285452187fe73a2604dcae8 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 17 Apr 2019 17:51:30 +0200 Subject: [PATCH 23/34] use IResource and Resource --- packages/@aws-cdk/aws-rds/lib/cluster-ref.ts | 4 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 28 +++++++------- .../@aws-cdk/aws-rds/lib/database-secret.ts | 4 +- packages/@aws-cdk/aws-rds/lib/instance.ts | 38 +++++++++---------- packages/@aws-cdk/aws-rds/lib/option-group.ts | 16 ++++---- .../@aws-cdk/aws-rds/lib/parameter-group.ts | 18 ++++----- .../@aws-cdk/aws-rds/lib/secret-rotation.ts | 6 +-- 7 files changed, 57 insertions(+), 57 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts index 32277c8059579..d78ecd351cb2a 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts @@ -1,12 +1,12 @@ import ec2 = require('@aws-cdk/aws-ec2'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import cdk = require('@aws-cdk/cdk'); +import { IResource } from '@aws-cdk/cdk'; import { Endpoint } from './endpoint'; /** * Create a clustered database with a given number of instances. */ -export interface IDatabaseCluster extends cdk.IConstruct, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget { +export interface IDatabaseCluster extends IResource, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget { /** * Identifier of the cluster */ diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 2a085f8817b85..a8aed42a8b9c9 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -1,7 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import cdk = require('@aws-cdk/cdk'); +import { CfnOutput, Construct, DeletionPolicy, Resource, StringListCfnOutput } from '@aws-cdk/cdk'; import { DatabaseClusterImportProps, IDatabaseCluster } from './cluster-ref'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; @@ -110,17 +110,17 @@ export interface DatabaseClusterProps { * * @default Retain */ - readonly deleteReplacePolicy?: cdk.DeletionPolicy + readonly deleteReplacePolicy?: DeletionPolicy } /** * A new or imported clustered database. */ -export abstract class DatabaseClusterBase extends cdk.Construct implements IDatabaseCluster { +export abstract class DatabaseClusterBase extends Resource implements IDatabaseCluster { /** * Import an existing DatabaseCluster from properties */ - public static import(scope: cdk.Construct, id: string, props: DatabaseClusterImportProps): IDatabaseCluster { + public static import(scope: Construct, id: string, props: DatabaseClusterImportProps): IDatabaseCluster { return new ImportedDatabaseCluster(scope, id, props); } @@ -230,7 +230,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu */ private readonly vpcSubnets?: ec2.SubnetSelection; - constructor(scope: cdk.Construct, id: string, props: DatabaseClusterProps) { + constructor(scope: Construct, id: string, props: DatabaseClusterProps) { super(scope, id); this.vpc = props.instanceProps.vpc; @@ -289,7 +289,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu storageEncrypted: props.kmsKey ? true : props.storageEncrypted }); - const deleteReplacePolicy = props.deleteReplacePolicy || cdk.DeletionPolicy.Retain; + const deleteReplacePolicy = props.deleteReplacePolicy || DeletionPolicy.Retain; cluster.options.deletionPolicy = deleteReplacePolicy; cluster.options.updateReplacePolicy = deleteReplacePolicy; @@ -369,13 +369,13 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu public export(): DatabaseClusterImportProps { // tslint:disable:max-line-length return { - port: new cdk.CfnOutput(this, 'Port', { value: this.clusterEndpoint.port, }).makeImportValue().toString(), - securityGroupId: new cdk.CfnOutput(this, 'SecurityGroupId', { value: this.securityGroupId, }).makeImportValue().toString(), - clusterIdentifier: new cdk.CfnOutput(this, 'ClusterIdentifier', { value: this.clusterIdentifier, }).makeImportValue().toString(), - instanceIdentifiers: new cdk.StringListCfnOutput(this, 'InstanceIdentifiers', { values: this.instanceIdentifiers }).makeImportValues().map(x => x.toString()), - clusterEndpointAddress: new cdk.CfnOutput(this, 'ClusterEndpointAddress', { value: this.clusterEndpoint.hostname, }).makeImportValue().toString(), - readerEndpointAddress: new cdk.CfnOutput(this, 'ReaderEndpointAddress', { value: this.readerEndpoint.hostname, }).makeImportValue().toString(), - instanceEndpointAddresses: new cdk.StringListCfnOutput(this, 'InstanceEndpointAddresses', { values: this.instanceEndpoints.map(e => e.hostname) }).makeImportValues().map(x => x.toString()), + port: new CfnOutput(this, 'Port', { value: this.clusterEndpoint.port, }).makeImportValue().toString(), + securityGroupId: new CfnOutput(this, 'SecurityGroupId', { value: this.securityGroupId, }).makeImportValue().toString(), + clusterIdentifier: new CfnOutput(this, 'ClusterIdentifier', { value: this.clusterIdentifier, }).makeImportValue().toString(), + instanceIdentifiers: new StringListCfnOutput(this, 'InstanceIdentifiers', { values: this.instanceIdentifiers }).makeImportValues().map(x => x.toString()), + clusterEndpointAddress: new CfnOutput(this, 'ClusterEndpointAddress', { value: this.clusterEndpoint.hostname, }).makeImportValue().toString(), + readerEndpointAddress: new CfnOutput(this, 'ReaderEndpointAddress', { value: this.readerEndpoint.hostname, }).makeImportValue().toString(), + instanceEndpointAddresses: new StringListCfnOutput(this, 'InstanceEndpointAddresses', { values: this.instanceEndpoints.map(e => e.hostname) }).makeImportValues().map(x => x.toString()), }; // tslint:enable:max-line-length } @@ -432,7 +432,7 @@ class ImportedDatabaseCluster extends DatabaseClusterBase implements IDatabaseCl */ public readonly securityGroupId: string; - constructor(scope: cdk.Construct, name: string, private readonly props: DatabaseClusterImportProps) { + constructor(scope: Construct, name: string, private readonly props: DatabaseClusterImportProps) { super(scope, name); this.securityGroupId = props.securityGroupId; diff --git a/packages/@aws-cdk/aws-rds/lib/database-secret.ts b/packages/@aws-cdk/aws-rds/lib/database-secret.ts index 875801c6695b5..48dabc74f7191 100644 --- a/packages/@aws-cdk/aws-rds/lib/database-secret.ts +++ b/packages/@aws-cdk/aws-rds/lib/database-secret.ts @@ -1,6 +1,6 @@ import kms = require('@aws-cdk/aws-kms'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import cdk = require('@aws-cdk/cdk'); +import { Construct } from '@aws-cdk/cdk'; /** * Construction properties for a DatabaseSecret. @@ -23,7 +23,7 @@ export interface DatabaseSecretProps { * A database secret. */ export class DatabaseSecret extends secretsmanager.Secret { - constructor(scope: cdk.Construct, id: string, props: DatabaseSecretProps) { + constructor(scope: Construct, id: string, props: DatabaseSecretProps) { super(scope, id, { encryptionKey: props.encryptionKey, generateSecretString: { diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index d55cbfc43c10d..8df83217e5a0e 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -5,7 +5,7 @@ import kms = require('@aws-cdk/aws-kms'); import lambda = require('@aws-cdk/aws-lambda'); import logs = require('@aws-cdk/aws-logs'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import cdk = require('@aws-cdk/cdk'); +import { CfnOutput, Construct, DeletionPolicy, IResource, Resource, SecretValue } from '@aws-cdk/cdk'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; import { IOptionGroup} from './option-group'; @@ -14,7 +14,7 @@ import { DatabaseClusterEngine } from './props'; import { CfnDBInstance, CfnDBInstanceProps, CfnDBSubnetGroup } from './rds.generated'; import { SecretRotation, SecretRotationApplication, SecretRotationOptions } from './secret-rotation'; -export interface IDatabaseInstance extends cdk.IConstruct, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget { +export interface IDatabaseInstance extends IResource, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget { /** * The instance identifier. */ @@ -50,11 +50,11 @@ export interface IDatabaseInstance extends cdk.IConstruct, ec2.IConnectable, sec /** * A new or imported database instance. */ -export abstract class DatabaseInstanceBase extends cdk.Construct implements IDatabaseInstance { +export abstract class DatabaseInstanceBase extends Resource implements IDatabaseInstance { /** * Import an existing database instance. */ - public static import(scope: cdk.Construct, id: string, props: DatabaseInstanceImportProps): IDatabaseInstance { + public static import(scope: Construct, id: string, props: DatabaseInstanceImportProps): IDatabaseInstance { return new ImportedDatabaseInstance(scope, id, props); } @@ -400,7 +400,7 @@ export interface DatabaseInstanceNewProps { * * @default Retain */ - readonly deleteReplacePolicy?: cdk.DeletionPolicy + readonly deleteReplacePolicy?: DeletionPolicy } /** @@ -417,7 +417,7 @@ export abstract class DatabaseInstanceNew extends DatabaseInstanceBase implement private readonly cloudwatchLogsExports?: string[]; private readonly cloudwatchLogsRetention?: logs.RetentionDays; - constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceNewProps) { + constructor(scope: Construct, id: string, props: DatabaseInstanceNewProps) { super(scope, id); this.vpc = props.vpc; @@ -493,10 +493,10 @@ export abstract class DatabaseInstanceNew extends DatabaseInstanceBase implement public export(): DatabaseInstanceImportProps { return { - instanceIdentifier: new cdk.CfnOutput(this, 'InstanceId', { value: this.instanceIdentifier }).makeImportValue().toString(), - endpointAddress: new cdk.CfnOutput(this, 'EndpointAddress', { value: this.instanceEndpoint.hostname }).makeImportValue().toString(), - port: new cdk.CfnOutput(this, 'Port', { value: this.instanceEndpoint.port }).makeImportValue().toString(), - securityGroupId: new cdk.CfnOutput(this, 'SecurityGroupId', { value: this.securityGroupId, }).makeImportValue().toString() + instanceIdentifier: new CfnOutput(this, 'InstanceId', { value: this.instanceIdentifier }).makeImportValue().toString(), + endpointAddress: new CfnOutput(this, 'EndpointAddress', { value: this.instanceEndpoint.hostname }).makeImportValue().toString(), + port: new CfnOutput(this, 'Port', { value: this.instanceEndpoint.port }).makeImportValue().toString(), + securityGroupId: new CfnOutput(this, 'SecurityGroupId', { value: this.securityGroupId, }).makeImportValue().toString() }; } @@ -562,7 +562,7 @@ export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { * * @default a Secrets Manager generated password */ - readonly masterUserPassword?: cdk.SecretValue; + readonly masterUserPassword?: SecretValue; /** * The KMS key to use to encrypt the secret for the master user password. @@ -596,7 +596,7 @@ export abstract class DatabaseInstanceSource extends DatabaseInstanceNew impleme private readonly secretRotationApplication: SecretRotationApplication; - constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceSourceProps) { + constructor(scope: Construct, id: string, props: DatabaseInstanceSourceProps) { super(scope, id, props); this.secretRotationApplication = props.engine.secretRotationApplication; @@ -673,7 +673,7 @@ export class DatabaseInstance extends DatabaseInstanceSource implements IDatabas public readonly connections: ec2.Connections; public readonly secret?: secretsmanager.ISecret; - constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceProps) { + constructor(scope: Construct, id: string, props: DatabaseInstanceProps) { super(scope, id, props); let secret; @@ -700,7 +700,7 @@ export class DatabaseInstance extends DatabaseInstanceSource implements IDatabas this.instanceIdentifier = instance.dbInstanceId; this.instanceEndpoint = new Endpoint(instance.dbInstanceEndpointAddress, instance.dbInstanceEndpointPort); - const deleteReplacePolicy = props.deleteReplacePolicy || cdk.DeletionPolicy.Retain; + const deleteReplacePolicy = props.deleteReplacePolicy || DeletionPolicy.Retain; instance.options.deletionPolicy = deleteReplacePolicy; instance.options.updateReplacePolicy = deleteReplacePolicy; @@ -756,7 +756,7 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme public readonly connections: ec2.Connections; public readonly secret?: secretsmanager.ISecret; - constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceFromSnapshotProps) { + constructor(scope: Construct, id: string, props: DatabaseInstanceFromSnapshotProps) { super(scope, id, props); if (props.generateMasterUserPassword && !props.masterUsername) { @@ -785,7 +785,7 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme this.instanceIdentifier = instance.dbInstanceId; this.instanceEndpoint = new Endpoint(instance.dbInstanceEndpointAddress, instance.dbInstanceEndpointPort); - const deleteReplacePolicy = props.deleteReplacePolicy || cdk.DeletionPolicy.Retain; + const deleteReplacePolicy = props.deleteReplacePolicy || DeletionPolicy.Retain; instance.options.deletionPolicy = deleteReplacePolicy; instance.options.updateReplacePolicy = deleteReplacePolicy; @@ -840,7 +840,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements public readonly instanceEndpoint: Endpoint; public readonly connections: ec2.Connections; - constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceReadReplicaProps) { + constructor(scope: Construct, id: string, props: DatabaseInstanceReadReplicaProps) { super(scope, id, props); const instance = new CfnDBInstance(this, 'Resource', { @@ -853,7 +853,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements this.instanceIdentifier = instance.dbInstanceId; this.instanceEndpoint = new Endpoint(instance.dbInstanceEndpointAddress, instance.dbInstanceEndpointPort); - const deleteReplacePolicy = props.deleteReplacePolicy || cdk.DeletionPolicy.Retain; + const deleteReplacePolicy = props.deleteReplacePolicy || DeletionPolicy.Retain; instance.options.deletionPolicy = deleteReplacePolicy; instance.options.updateReplacePolicy = deleteReplacePolicy; @@ -900,7 +900,7 @@ class ImportedDatabaseInstance extends DatabaseInstanceBase implements IDatabase public readonly securityGroupId: string; public readonly connections: ec2.Connections; - constructor(scope: cdk.Construct, id: string, private readonly props: DatabaseInstanceImportProps) { + constructor(scope: Construct, id: string, private readonly props: DatabaseInstanceImportProps) { super(scope, id); this.instanceIdentifier = props.instanceIdentifier; diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index c13ff6c304f2b..f5c82abce3170 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -1,12 +1,12 @@ import ec2 = require('@aws-cdk/aws-ec2'); -import cdk = require('@aws-cdk/cdk'); +import { CfnOutput, Construct, IResource, Resource } from '@aws-cdk/cdk'; import { DatabaseInstanceEngine } from './instance'; import { CfnOptionGroup } from './rds.generated'; /** * An option group */ -export interface IOptionGroup extends cdk.IConstruct { +export interface IOptionGroup extends IResource { /** * The name of the option group. */ @@ -84,11 +84,11 @@ export interface OptionGroupProps { readonly configurations: OptionConfiguration[]; } -export class OptionGroup extends cdk.Construct implements IOptionGroup { +export class OptionGroup extends Resource implements IOptionGroup { /** * Import an existing option group. */ - public static import(scope: cdk.Construct, id: string, props: OptionGroupImportProps): IOptionGroup { + public static import(scope: Construct, id: string, props: OptionGroupImportProps): IOptionGroup { return new ImportedOptionGroup(scope, id, props); } @@ -102,7 +102,7 @@ export class OptionGroup extends cdk.Construct implements IOptionGroup { */ public readonly optionConnections: { [key: string]: ec2.Connections } = {}; - constructor(scope: cdk.Construct, id: string, props: OptionGroupProps) { + constructor(scope: Construct, id: string, props: OptionGroupProps) { super(scope, id); const optionGroup = new CfnOptionGroup(this, 'Resource', { @@ -117,7 +117,7 @@ export class OptionGroup extends cdk.Construct implements IOptionGroup { public export(): OptionGroupImportProps { return { - optionGroupName: new cdk.CfnOutput(this, 'OptionGroupName', { value: this.optionGroupName }).makeImportValue().toString(), + optionGroupName: new CfnOutput(this, 'OptionGroupName', { value: this.optionGroupName }).makeImportValue().toString(), }; } @@ -169,10 +169,10 @@ export interface OptionGroupImportProps { readonly optionGroupName: string; } -class ImportedOptionGroup extends cdk.Construct implements IOptionGroup { +class ImportedOptionGroup extends Construct implements IOptionGroup { public readonly optionGroupName: string; - constructor(scope: cdk.Construct, id: string, private readonly props: OptionGroupImportProps) { + constructor(scope: Construct, id: string, private readonly props: OptionGroupImportProps) { super(scope, id); this.optionGroupName = props.optionGroupName; diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts index 8112bca017382..8ca60e8c8d6cd 100644 --- a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -1,10 +1,10 @@ -import cdk = require('@aws-cdk/cdk'); +import { CfnOutput, Construct, IResource, Resource } from '@aws-cdk/cdk'; import { CfnDBClusterParameterGroup, CfnDBParameterGroup } from './rds.generated'; /** * A parameter group */ -export interface IParameterGroup extends cdk.IConstruct { +export interface IParameterGroup extends IResource { /** * The name of this parameter group */ @@ -41,11 +41,11 @@ export interface ParameterGroupProps { /** * A new cluster or instance parameter group */ -export abstract class ParameterGroupBase extends cdk.Construct implements IParameterGroup { +export abstract class ParameterGroupBase extends Resource implements IParameterGroup { /** * Imports a parameter group */ - public static import(scope: cdk.Construct, id: string, props: ParameterGroupImportProps): IParameterGroup { + public static import(scope: Construct, id: string, props: ParameterGroupImportProps): IParameterGroup { return new ImportedParameterGroup(scope, id, props); } @@ -59,7 +59,7 @@ export abstract class ParameterGroupBase extends cdk.Construct implements IParam */ public export(): ParameterGroupImportProps { return { - parameterGroupName: new cdk.CfnOutput(this, 'ParameterGroupName', { value: this.parameterGroupName }).makeImportValue().toString() + parameterGroupName: new CfnOutput(this, 'ParameterGroupName', { value: this.parameterGroupName }).makeImportValue().toString() }; } } @@ -73,7 +73,7 @@ export class ParameterGroup extends ParameterGroupBase { */ public readonly parameterGroupName: string; - constructor(scope: cdk.Construct, id: string, props: ParameterGroupProps) { + constructor(scope: Construct, id: string, props: ParameterGroupProps) { super(scope, id); const resource = new CfnDBParameterGroup(this, 'Resource', { @@ -95,7 +95,7 @@ export class ClusterParameterGroup extends ParameterGroupBase { */ public readonly parameterGroupName: string; - constructor(scope: cdk.Construct, id: string, props: ParameterGroupProps) { + constructor(scope: Construct, id: string, props: ParameterGroupProps) { super(scope, id); const resource = new CfnDBClusterParameterGroup(this, 'Resource', { @@ -121,13 +121,13 @@ export interface ParameterGroupImportProps { /** * An imported cluster or instance parameter group */ -class ImportedParameterGroup extends cdk.Construct implements IParameterGroup { +class ImportedParameterGroup extends Construct implements IParameterGroup { /** * The name of the parameter group */ public readonly parameterGroupName: string; - constructor(scope: cdk.Construct, id: string, private readonly props: ParameterGroupImportProps) { + constructor(scope: Construct, id: string, private readonly props: ParameterGroupImportProps) { super(scope, id); this.parameterGroupName = props.parameterGroupName; diff --git a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts index cd7b50eaa3144..52dea3dd0618c 100644 --- a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts @@ -2,7 +2,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import lambda = require('@aws-cdk/aws-lambda'); import serverless = require('@aws-cdk/aws-sam'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import cdk = require('@aws-cdk/cdk'); +import { Construct } from '@aws-cdk/cdk'; /** * A secret rotation serverless application. @@ -92,8 +92,8 @@ export interface SecretRotationProps extends SecretRotationOptions { /** * Secret rotation for a database instance or cluster. */ -export class SecretRotation extends cdk.Construct { - constructor(scope: cdk.Construct, id: string, props: SecretRotationProps) { +export class SecretRotation extends Construct { + constructor(scope: Construct, id: string, props: SecretRotationProps) { super(scope, id); if (!props.target.connections.defaultPortRange) { From 4578ccae97ce19ab6526dd432cc11c08e186539f Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 24 Apr 2019 16:29:35 +0200 Subject: [PATCH 24/34] use aws-events-targets in tests --- packages/@aws-cdk/aws-rds/package.json | 1 + packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts | 3 ++- packages/@aws-cdk/aws-rds/test/test.instance.ts | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index 5efb3144cef37..a0616031be9a3 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -60,6 +60,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-events-targets": "^0.28.0", "cdk-build-tools": "^0.28.0", "cdk-integ-tools": "^0.28.0", "cfn2ts": "^0.28.0", 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 76fc23c244768..ce78c46e97537 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -1,5 +1,6 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); +import targets = require('@aws-cdk/aws-events-targets'); import lambda = require('@aws-cdk/aws-lambda'); import logs = require('@aws-cdk/aws-logs'); import cdk = require('@aws-cdk/cdk'); @@ -96,7 +97,7 @@ class DatabaseInstanceStack extends cdk.Stack { runtime: lambda.Runtime.NodeJS810 }); - availabilityRule.addTarget(fn); + availabilityRule.addTarget(new targets.LambdaFunction(fn)); /// !hide } } diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index 2eb6202271eb0..5fd58a1ef9ad0 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -1,5 +1,6 @@ import { countResources, expect, haveResource, ResourcePart } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); +import targets = require('@aws-cdk/aws-events-targets'); import lambda = require('@aws-cdk/aws-lambda'); import logs = require('@aws-cdk/aws-logs'); import cdk = require('@aws-cdk/cdk'); @@ -333,7 +334,7 @@ export = { }); // WHEN - instance.onEvent('InstanceEvent', fn); + instance.onEvent('InstanceEvent', new targets.LambdaFunction(fn)); // THEN expect(stack).to(haveResource('AWS::Events::Rule', { From b589ed444ff1de17fb6379ebaf305264d7b09c1e Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 24 Apr 2019 16:35:18 +0200 Subject: [PATCH 25/34] fix awslint:props-struct-name --- packages/@aws-cdk/aws-rds/lib/parameter-group.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts index 8ca60e8c8d6cd..90913ad3f94c9 100644 --- a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -86,6 +86,13 @@ export class ParameterGroup extends ParameterGroupBase { } } +/** + * Construction properties for a ClusterParameterGroup + */ +// tslint:disable-next-line:no-empty-interface +export interface ClusterParameterGroupProps extends ParameterGroupProps { + +} /** * A cluster parameter group */ @@ -95,7 +102,7 @@ export class ClusterParameterGroup extends ParameterGroupBase { */ public readonly parameterGroupName: string; - constructor(scope: Construct, id: string, props: ParameterGroupProps) { + constructor(scope: Construct, id: string, props: ClusterParameterGroupProps) { super(scope, id); const resource = new CfnDBClusterParameterGroup(this, 'Resource', { From ee8d0674a22bb16bd71b0d50bf6cf542d716b652 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 24 Apr 2019 16:45:12 +0200 Subject: [PATCH 26/34] update README --- packages/@aws-cdk/aws-rds/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 986d7c81fecd2..a91ab80da5440 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -75,14 +75,14 @@ rule.addTarget(lambdaFunction); ### Connecting -To control who can access the cluster/instance, use the `.connections` attribute. RDS database have +To control who can access the cluster or instance, use the `.connections` attribute. RDS databases have a default port, so you don't need to specify the port: ```ts cluster.connections.allowFromAnyIpv4('Open to the world'); ``` -The endpoints to access your cluster database will be available as the `.clusterEndpoint` and `.readerEndpoint` +The endpoints to access your database cluster will be available as the `.clusterEndpoint` and `.readerEndpoint` attributes: ```ts @@ -95,15 +95,15 @@ const address = instance.instanceEndpoint.socketAddress; // "HOSTNAME:PORT" ``` ### Rotating master password -When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically both for a cluster and an instance: +When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically: [example of setting up master password rotation for a cluster](test/integ.cluster-rotation.lit.ts) Rotation of the master password is also supported for an existing cluster: ```ts -new RotationSingleUser(stack, 'Rotation', { +new SecretRotation(stack, 'Rotation', { secret: importedSecret, - engine: DatabaseEngine.Oracle, + application: SecretRotationApplication.OracleRotationSingleUser target: importedCluster, // or importedInstance vpc: importedVpc, }) @@ -122,7 +122,7 @@ The `importedSecret` must be a JSON string with the following format: ``` ### Metrics -Database instances expose [metrics (cloudwatch.Metric)](https://github.com/awslabs/aws-cdk/blob/master/packages/%40aws-cdk/aws-cloudwatch/README.md): +Database instances expose metrics (`cloudwatch.Metric`): ```ts // The number of database connections in use (average over 5 minutes) const dbConnections = instance.metricDatabaseConnections(); From c2a2c926c84894262ed0c841ef37ebcbfe7c7ac0 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 24 Apr 2019 16:57:11 +0200 Subject: [PATCH 27/34] remove awkward engine.engine --- packages/@aws-cdk/aws-rds/lib/cluster.ts | 4 ++-- packages/@aws-cdk/aws-rds/lib/instance.ts | 2 +- packages/@aws-cdk/aws-rds/lib/option-group.ts | 8 ++++---- packages/@aws-cdk/aws-rds/lib/props.ts | 6 +++--- packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts | 2 +- packages/@aws-cdk/aws-rds/test/test.instance.ts | 2 +- packages/@aws-cdk/aws-rds/test/test.option-group.ts | 8 ++++---- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index a8aed42a8b9c9..d288b95510b6c 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -267,7 +267,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu const cluster = new CfnDBCluster(this, 'Resource', { // Basic - engine: props.engine.engine, + engine: props.engine.name, dbClusterIdentifier: props.clusterIdentifier, dbSubnetGroupName: subnetGroup.ref, vpcSecurityGroupIds: [this.securityGroupId], @@ -321,7 +321,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, { // Link to cluster - engine: props.engine.engine, + engine: props.engine.name, dbClusterIdentifier: cluster.ref, dbInstanceIdentifier: instanceIdentifier, // Instance properties diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 8df83217e5a0e..5e827c8b0092f 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -607,7 +607,7 @@ export abstract class DatabaseInstanceSource extends DatabaseInstanceNew impleme allowMajorVersionUpgrade: props.allowMajorVersionUpgrade, dbName: props.databaseName, dbParameterGroupName: props.parameterGroup && props.parameterGroup.parameterGroupName, - engine: props.engine.engine, + engine: props.engine.name, engineVersion: props.engineVersion, licenseModel: props.licenseModel, timezone: props.timezone diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index f5c82abce3170..5d0b097a986d7 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -61,9 +61,9 @@ export interface OptionConfiguration { */ export interface OptionGroupProps { /** - * The name of the database engine that this option group is associated with. + * The database engine that this option group is associated with. */ - readonly engineName: DatabaseInstanceEngine + readonly engine: DatabaseInstanceEngine; /** * The major version number of the database engine that this option group @@ -106,9 +106,9 @@ export class OptionGroup extends Resource implements IOptionGroup { super(scope, id); const optionGroup = new CfnOptionGroup(this, 'Resource', { - engineName: props.engineName.engine, + engineName: props.engine.name, majorEngineVersion: props.majorEngineVersion, - optionGroupDescription: props.description || `Option group for ${props.engineName.engine} ${props.majorEngineVersion}`, + optionGroupDescription: props.description || `Option group for ${props.engine.name} ${props.majorEngineVersion}`, optionConfigurations: this.renderConfigurations(props.configurations) }); diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index 9b8222bf404e6..253e2bc1f1664 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -15,15 +15,15 @@ export class DatabaseClusterEngine { /** * The engine. */ - public readonly engine: string; + public readonly name: string; /** * The secret rotation application. */ public readonly secretRotationApplication: SecretRotationApplication; - constructor(engine: string, secretRotationApplication: SecretRotationApplication) { - this.engine = engine; + constructor(name: string, secretRotationApplication: SecretRotationApplication) { + this.name = name; this.secretRotationApplication = secretRotationApplication; } } 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 ce78c46e97537..9dd6757bfb9fc 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -25,7 +25,7 @@ class DatabaseInstanceStack extends cdk.Stack { /// Add XMLDB and OEM with option group const optionGroup = new rds.OptionGroup(this, 'OptionGroup', { - engineName: rds.DatabaseInstanceEngine.OracleSE1, + engine: rds.DatabaseInstanceEngine.OracleSE1, majorEngineVersion: '11.2', configurations: [ { diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index 5fd58a1ef9ad0..b75f441da367b 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -191,7 +191,7 @@ export = { const vpc = new ec2.VpcNetwork(stack, 'VPC'); const optionGroup = new rds.OptionGroup(stack, 'OptionGroup', { - engineName: rds.DatabaseInstanceEngine.OracleSE1, + engine: rds.DatabaseInstanceEngine.OracleSE1, majorEngineVersion: '11.2', configurations: [ { 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 95230b1beefb0..94cc73a92e501 100644 --- a/packages/@aws-cdk/aws-rds/test/test.option-group.ts +++ b/packages/@aws-cdk/aws-rds/test/test.option-group.ts @@ -11,7 +11,7 @@ export = { // WHEN new OptionGroup(stack, 'Options', { - engineName: DatabaseInstanceEngine.OracleSE1, + engine: DatabaseInstanceEngine.OracleSE1, majorEngineVersion: '11.2', configurations: [ { @@ -42,7 +42,7 @@ export = { // WHEN const optionGroup = new OptionGroup(stack, 'Options', { - engineName: DatabaseInstanceEngine.OracleSE1, + engine: DatabaseInstanceEngine.OracleSE1, majorEngineVersion: '11.2', configurations: [ { @@ -100,7 +100,7 @@ export = { // THEN test.throws(() => new OptionGroup(stack, 'Options', { - engineName: DatabaseInstanceEngine.OracleSE1, + engine: DatabaseInstanceEngine.OracleSE1, majorEngineVersion: '11.2', configurations: [ { @@ -117,7 +117,7 @@ export = { // GIVEN const stack = new cdk.Stack(); const group = new OptionGroup(stack, 'Options', { - engineName: DatabaseInstanceEngine.OracleSE1, + engine: DatabaseInstanceEngine.OracleSE1, majorEngineVersion: '11.2', configurations: [ { From e06616fdaf7e22c5f598513ab7b12aaa09bae6f1 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 29 Apr 2019 11:47:42 +0200 Subject: [PATCH 28/34] improve comment --- packages/@aws-cdk/aws-rds/lib/secret-rotation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts index 52dea3dd0618c..be900051b8df2 100644 --- a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts @@ -130,8 +130,8 @@ export class SecretRotation extends Construct { }), }); - // Cannot use rotationLambda.addPermission because it currently does not - // return a cdk.Construct and we need to add a dependency. + // Cannot use rotationLambda.addPermission because it's a no-op on imported + // functions. const permission = new lambda.CfnPermission(this, 'Permission', { action: 'lambda:InvokeFunction', functionName: rotationFunctionName, From 25e406f232eefecaa6767714500278b89e570d4f Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 1 May 2019 22:33:34 +0200 Subject: [PATCH 29/34] private base and local import --- packages/@aws-cdk/aws-rds/lib/instance.ts | 4 +- packages/@aws-cdk/aws-rds/lib/option-group.ts | 38 ++++----- .../@aws-cdk/aws-rds/lib/parameter-group.ts | 85 ++++++++----------- packages/@aws-cdk/aws-rds/package.json | 5 ++ 4 files changed, 57 insertions(+), 75 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 5e827c8b0092f..31eb7faf7627f 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -406,7 +406,7 @@ export interface DatabaseInstanceNewProps { /** * A new database instance. */ -export abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IDatabaseInstance { +abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IDatabaseInstance { public readonly securityGroupId: string; public readonly vpc: ec2.IVpcNetwork; @@ -589,7 +589,7 @@ export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { /** * A new source database instance (not a read replica) */ -export abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDatabaseInstance { +abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDatabaseInstance { public abstract readonly secret?: secretsmanager.ISecret; protected readonly sourceCfnProps: CfnDBInstanceProps; diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index 5d0b097a986d7..87bd8353b0b4d 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -18,6 +18,16 @@ export interface IOptionGroup extends IResource { export(): OptionGroupImportProps; } +/** + * Construction properties for an imported option group. + */ +export interface OptionGroupImportProps { + /** + * The name of the option group. + */ + readonly optionGroupName: string; +} + /** * Configuration properties for an option. */ @@ -89,7 +99,12 @@ export class OptionGroup extends Resource implements IOptionGroup { * Import an existing option group. */ public static import(scope: Construct, id: string, props: OptionGroupImportProps): IOptionGroup { - return new ImportedOptionGroup(scope, id, props); + class Import extends Construct { + public readonly optionGroupName = props.optionGroupName; + + public export() { return props; } + } + return new Import(scope, id); } /** @@ -161,24 +176,3 @@ export class OptionGroup extends Resource implements IOptionGroup { return configs; } } - -export interface OptionGroupImportProps { - /** - * The name of the option group. - */ - readonly optionGroupName: string; -} - -class ImportedOptionGroup extends Construct implements IOptionGroup { - public readonly optionGroupName: string; - - constructor(scope: Construct, id: string, private readonly props: OptionGroupImportProps) { - super(scope, id); - - this.optionGroupName = props.optionGroupName; - } - - public export() { - return this.props; - } -} diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts index 90913ad3f94c9..4b864f7719205 100644 --- a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -17,36 +17,30 @@ export interface IParameterGroup extends IResource { } /** - * Properties for a parameter group + * Construction properties for an imported parameter group */ -export interface ParameterGroupProps { - /** - * Database family of this parameter group - */ - readonly family: string; - - /** - * Description for this parameter group - * - * @default a CDK generated description - */ - readonly description?: string; - +export interface ParameterGroupImportProps { /** - * The parameters in this parameter group + * The name of the parameter group */ - readonly parameters: { [key: string]: string }; + readonly parameterGroupName: string; } /** * A new cluster or instance parameter group */ -export abstract class ParameterGroupBase extends Resource implements IParameterGroup { +abstract class ParameterGroupBase extends Resource implements IParameterGroup { /** * Imports a parameter group */ public static import(scope: Construct, id: string, props: ParameterGroupImportProps): IParameterGroup { - return new ImportedParameterGroup(scope, id, props); + class Import extends Construct implements IParameterGroup { + public readonly parameterGroupName = props.parameterGroupName; + + public export() { return props; } + } + + return new Import(scope, id); } /** @@ -64,6 +58,28 @@ export abstract class ParameterGroupBase extends Resource implements IParameterG } } +/** + * Properties for a parameter group + */ +export interface ParameterGroupProps { + /** + * Database family of this parameter group + */ + readonly family: string; + + /** + * Description for this parameter group + * + * @default a CDK generated description + */ + readonly description?: string; + + /** + * The parameters in this parameter group + */ + readonly parameters: { [key: string]: string }; +} + /** * A parameter group */ @@ -114,36 +130,3 @@ export class ClusterParameterGroup extends ParameterGroupBase { this.parameterGroupName = resource.dbClusterParameterGroupName; } } - -/** - * Construction properties for an ImportedParameterGroup - */ -export interface ParameterGroupImportProps { - /** - * The name of the parameter group - */ - readonly parameterGroupName: string; -} - -/** - * An imported cluster or instance parameter group - */ -class ImportedParameterGroup extends Construct implements IParameterGroup { - /** - * The name of the parameter group - */ - public readonly parameterGroupName: string; - - constructor(scope: Construct, id: string, private readonly props: ParameterGroupImportProps) { - super(scope, id); - - this.parameterGroupName = props.parameterGroupName; - } - - /** - * Exports this parameter group from the stack - */ - public export(): ParameterGroupImportProps { - return this.props; - } -} diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index 00c764e5832da..1286bff960f79 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -93,5 +93,10 @@ }, "engines": { "node": ">= 8.10.0" + }, + "awslint": { + "exclude": [ + "construct-base-is-private:@aws-cdk/aws-rds.DatabaseInstanceBase" + ] } } From 13fb8c72b794b901b775ef459f7e23c1354bc1b4 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 2 May 2019 17:47:17 +0200 Subject: [PATCH 30/34] fromOptionGroupName and fromParameterGroupName --- packages/@aws-cdk/aws-rds/lib/option-group.ts | 14 ++++++++------ packages/@aws-cdk/aws-rds/lib/parameter-group.ts | 14 ++++++++------ .../@aws-cdk/aws-rds/test/test.option-group.ts | 2 +- .../@aws-cdk/aws-rds/test/test.parameter-group.ts | 4 ++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index 87bd8353b0b4d..d5bccccbcb3d7 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -15,13 +15,13 @@ export interface IOptionGroup extends IResource { /** * Exports this option group from the stack. */ - export(): OptionGroupImportProps; + export(): OptionGroupAttributes; } /** * Construction properties for an imported option group. */ -export interface OptionGroupImportProps { +export interface OptionGroupAttributes { /** * The name of the option group. */ @@ -98,11 +98,13 @@ export class OptionGroup extends Resource implements IOptionGroup { /** * Import an existing option group. */ - public static import(scope: Construct, id: string, props: OptionGroupImportProps): IOptionGroup { + public static fromOptionGroupName(scope: Construct, id: string, optionGroupName: string): IOptionGroup { class Import extends Construct { - public readonly optionGroupName = props.optionGroupName; + public readonly optionGroupName = optionGroupName; - public export() { return props; } + public export(): OptionGroupAttributes { + return { optionGroupName }; + } } return new Import(scope, id); } @@ -130,7 +132,7 @@ export class OptionGroup extends Resource implements IOptionGroup { this.optionGroupName = optionGroup.optionGroupName; } - public export(): OptionGroupImportProps { + public export(): OptionGroupAttributes { return { optionGroupName: new CfnOutput(this, 'OptionGroupName', { value: this.optionGroupName }).makeImportValue().toString(), }; diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts index 4b864f7719205..b417c353a3728 100644 --- a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -13,13 +13,13 @@ export interface IParameterGroup extends IResource { /** * Exports this parameter group from the stack */ - export(): ParameterGroupImportProps; + export(): ParameterGroupAttributes; } /** * Construction properties for an imported parameter group */ -export interface ParameterGroupImportProps { +export interface ParameterGroupAttributes { /** * The name of the parameter group */ @@ -33,11 +33,13 @@ abstract class ParameterGroupBase extends Resource implements IParameterGroup { /** * Imports a parameter group */ - public static import(scope: Construct, id: string, props: ParameterGroupImportProps): IParameterGroup { + public static fromParameterGroupName(scope: Construct, id: string, parameterGroupName: string): IParameterGroup { class Import extends Construct implements IParameterGroup { - public readonly parameterGroupName = props.parameterGroupName; + public readonly parameterGroupName = parameterGroupName; - public export() { return props; } + public export(): ParameterGroupAttributes { + return { parameterGroupName }; + } } return new Import(scope, id); @@ -51,7 +53,7 @@ abstract class ParameterGroupBase extends Resource implements IParameterGroup { /** * Exports this parameter group from the stack */ - public export(): ParameterGroupImportProps { + public export(): ParameterGroupAttributes { return { parameterGroupName: new CfnOutput(this, 'ParameterGroupName', { value: this.parameterGroupName }).makeImportValue().toString() }; 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 94cc73a92e501..430f1f1f1b4d8 100644 --- a/packages/@aws-cdk/aws-rds/test/test.option-group.ts +++ b/packages/@aws-cdk/aws-rds/test/test.option-group.ts @@ -128,7 +128,7 @@ export = { // WHEN const exported = group.export(); - const imported = OptionGroup.import(stack, 'ImportOptions', exported); + const imported = OptionGroup.fromOptionGroupName(stack, 'ImportOptions', exported.optionGroupName); // THEN test.deepEqual(stack.node.resolve(exported), { optionGroupName: { 'Fn::ImportValue': 'Stack:OptionsOptionGroupName16F3CF77' } }); diff --git a/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts b/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts index bc07ac699b4f1..94e07559026b9 100644 --- a/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/test/test.parameter-group.ts @@ -67,7 +67,7 @@ export = { // WHEN const exported = group.export(); - const imported = ClusterParameterGroup.import(stack, 'ImportParams', exported); + const imported = ClusterParameterGroup.fromParameterGroupName(stack, 'ImportParams', exported.parameterGroupName); // THEN test.deepEqual(stack.node.resolve(exported), { parameterGroupName: { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' } }); @@ -88,7 +88,7 @@ export = { // WHEN const exported = group.export(); - const imported = ClusterParameterGroup.import(stack, 'ImportParams', exported); + const imported = ClusterParameterGroup.fromParameterGroupName(stack, 'ImportParams', exported.parameterGroupName); // THEN test.deepEqual(stack.node.resolve(exported), { parameterGroupName: { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' } }); From e852acfd86566dd0190e8751bfb74ad0615b980f Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 3 May 2019 09:51:28 +0200 Subject: [PATCH 31/34] JSDoc --- packages/@aws-cdk/aws-rds/lib/option-group.ts | 2 +- packages/@aws-cdk/aws-rds/lib/parameter-group.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index d5bccccbcb3d7..8a7d22a64d5be 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -19,7 +19,7 @@ export interface IOptionGroup extends IResource { } /** - * Construction properties for an imported option group. + * Reference to an existing option group. */ export interface OptionGroupAttributes { /** diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts index b417c353a3728..2435730654f5d 100644 --- a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -17,7 +17,7 @@ export interface IParameterGroup extends IResource { } /** - * Construction properties for an imported parameter group + * Reference to an existing parameter group */ export interface ParameterGroupAttributes { /** From 28b0d5941db6d2681828eb55ec7939de1501122b Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 30 May 2019 21:38:17 +0200 Subject: [PATCH 32/34] instance parameter group for cluster --- packages/@aws-cdk/aws-rds/lib/cluster.ts | 1 + packages/@aws-cdk/aws-rds/lib/props.ts | 14 ++++++- .../@aws-cdk/aws-rds/test/test.cluster.ts | 38 ++++++++++++++++++- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 9f6372dd80a14..95f634e6a0fe0 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -356,6 +356,7 @@ export class DatabaseCluster extends DatabaseClusterBase { publiclyAccessible, // This is already set on the Cluster. Unclear to me whether it should be repeated or not. Better yes. dbSubnetGroupName: subnetGroup.ref, + dbParameterGroupName: props.instanceProps.parameterGroup && props.instanceProps.parameterGroup.parameterGroupName, }); instance.options.deletionPolicy = deleteReplacePolicy; diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index afeae3ef669b1..cde179018c92d 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -1,6 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); import { SecretValue } from '@aws-cdk/cdk'; +import { IParameterGroup } from './parameter-group'; import { SecretRotationApplication } from './secret-rotation'; /** @@ -46,13 +47,24 @@ export interface InstanceProps { /** * Where to place the instances within the VPC + * + * @default private subnets */ readonly vpcSubnets?: ec2.SubnetSelection; /** - * Security group. If not specified a new one will be created. + * Security group. + * + * @default a new security group is created. */ readonly securityGroup?: ec2.ISecurityGroup; + + /** + * The DB parameter group to associate with the instance. + * + * @default no parameter group + */ + readonly parameterGroup?: IParameterGroup; } /** diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index 1d969c16704f2..2924020ae6198 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -4,7 +4,7 @@ import kms = require('@aws-cdk/aws-kms'); import cdk = require('@aws-cdk/cdk'); import { SecretValue } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { ClusterParameterGroup, DatabaseCluster, DatabaseClusterEngine } from '../lib'; +import { ClusterParameterGroup, DatabaseCluster, DatabaseClusterEngine, ParameterGroup } from '../lib'; export = { 'check that instantiation works'(test: Test) { @@ -231,7 +231,41 @@ export = { })); test.done(); - } + }, + + 'cluster with instance parameter group'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const parameterGroup = new ParameterGroup(stack, 'ParameterGroup', { + family: 'hello', + parameters: { + key: 'value' + } + }); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.Aurora, + masterUser: { + username: 'admin', + }, + instanceProps: { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + parameterGroup, + vpc + } + }); + + expect(stack).to(haveResource('AWS::RDS::DBInstance', { + DBParameterGroupName: { + Ref: 'ParameterGroup5E32DECB' + } + })); + + test.done(); + + }, }; function testStack() { From d044e64d79bcd15afe1d75a465d848f0e3d10aa5 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 30 May 2019 22:04:00 +0200 Subject: [PATCH 33/34] set GP2 as default storage type --- packages/@aws-cdk/aws-rds/lib/instance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index f50239da5f13e..acfb4e095801e 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -498,7 +498,7 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData } const deletionProtection = props.deletionProtection !== undefined ? props.deletionProtection : true; - const storageType = props.storageType || StorageType.IO1; + const storageType = props.storageType || StorageType.GP2; const iops = storageType === StorageType.IO1 ? (props.iops || 1000) : undefined; this.cloudwatchLogsExports = props.cloudwatchLogsExports; From c904e9914c54ef0258e43e631b524e12f2ea5ae4 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 30 May 2019 22:22:58 +0200 Subject: [PATCH 34/34] update README for events --- packages/@aws-cdk/aws-rds/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index a91ab80da5440..9cff2ac89fdc0 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -69,8 +69,7 @@ To define Amazon CloudWatch event rules for database instances, use the `onEvent method: ```ts -const rule = instance.onEvent('InstanceEvent'); -rule.addTarget(lambdaFunction); +const rule = instance.onEvent('InstanceEvent', { target: new targets.LambdaFunction(fn) }); ``` ### Connecting