in packages/aws-cdk-lib/aws-docdb/lib/cluster.ts [467:635]
constructor(scope: Construct, id: string, props: DatabaseClusterProps) {
super(scope, id);
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
this.vpc = props.vpc;
this.vpcSubnets = props.vpcSubnets;
// Determine the subnet(s) to deploy the DocDB cluster to
const { subnetIds, internetConnectivityEstablished } = this.vpc.selectSubnets(this.vpcSubnets);
// DocDB clusters require a subnet group with subnets from at least two AZs.
// We cannot test whether the subnets are in different AZs, but at least we can test the amount.
// See https://docs.aws.amazon.com/documentdb/latest/developerguide/replication.html#replication.high-availability
if (subnetIds.length < 2) {
throw new ValidationError(`Cluster requires at least 2 subnets, got ${subnetIds.length}`, this);
}
const subnetGroup = new CfnDBSubnetGroup(this, 'Subnets', {
dbSubnetGroupDescription: `Subnets for ${id} database`,
subnetIds,
});
// Create the security group for the DB cluster
let securityGroup: ec2.ISecurityGroup;
if (props.securityGroup) {
securityGroup = props.securityGroup;
} else {
securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', {
description: 'DocumentDB security group',
vpc: this.vpc,
});
// HACK: Use an escape-hatch to apply a consistent removal policy to the
// security group so we don't get ValidationErrors when trying to delete the stack.
const securityGroupRemovalPolicy = this.getSecurityGroupRemovalPolicy(props);
(securityGroup.node.defaultChild as CfnResource).applyRemovalPolicy(securityGroupRemovalPolicy, {
applyToUpdateReplacePolicy: true,
});
}
this.securityGroupId = securityGroup.securityGroupId;
// Create the CloudwatchLogsConfiguration
const enableCloudwatchLogsExports: string[] = [];
if (props.exportAuditLogsToCloudWatch) {
enableCloudwatchLogsExports.push('audit');
}
if (props.exportProfilerLogsToCloudWatch) {
enableCloudwatchLogsExports.push('profiler');
}
// Create the secret manager secret if no password is specified
let secret: DatabaseSecret | undefined;
if (!props.masterUser.password) {
secret = new DatabaseSecret(this, 'Secret', {
username: props.masterUser.username,
encryptionKey: props.masterUser.kmsKey,
excludeCharacters: props.masterUser.excludeCharacters,
secretName: props.masterUser.secretName,
});
}
// Default to encrypted storage
const storageEncrypted = props.storageEncrypted ?? true;
if (props.kmsKey && !storageEncrypted) {
throw new ValidationError('KMS key supplied but storageEncrypted is false', this);
}
const validEngineVersionRegex = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
if (props.engineVersion !== undefined && !validEngineVersionRegex.test(props.engineVersion)) {
throw new ValidationError(`Invalid engine version: '${props.engineVersion}'. Engine version must be in the format x.y.z`, this);
}
if (
props.storageType === StorageType.IOPT1
&& props.engineVersion !== undefined
&& Number(props.engineVersion.split('.')[0]) < MIN_ENGINE_VERSION_FOR_IO_OPTIMIZED_STORAGE
) {
throw new ValidationError(`I/O-optimized storage is supported starting with engine version 5.0.0, got '${props.engineVersion}'`, this);
}
// Create the DocDB cluster
this.cluster = new CfnDBCluster(this, 'Resource', {
// Basic
engineVersion: props.engineVersion,
dbClusterIdentifier: props.dbClusterName,
dbSubnetGroupName: subnetGroup.ref,
port: props.port,
vpcSecurityGroupIds: [this.securityGroupId],
dbClusterParameterGroupName: props.parameterGroup?.parameterGroupName,
deletionProtection: props.deletionProtection,
// Admin
masterUsername: secret ? secret.secretValueFromJson('username').unsafeUnwrap() : props.masterUser.username,
masterUserPassword: secret
? secret.secretValueFromJson('password').unsafeUnwrap()
: props.masterUser.password!.unsafeUnwrap(), // Safe usage
// Backup
backupRetentionPeriod: props.backup?.retention?.toDays(),
preferredBackupWindow: props.backup?.preferredWindow,
preferredMaintenanceWindow: props.preferredMaintenanceWindow,
// EnableCloudwatchLogsExports
enableCloudwatchLogsExports: enableCloudwatchLogsExports.length > 0 ? enableCloudwatchLogsExports : undefined,
// Encryption
kmsKeyId: props.kmsKey?.keyArn,
storageEncrypted,
// Tags
copyTagsToSnapshot: props.copyTagsToSnapshot,
storageType: props.storageType,
});
this.cluster.applyRemovalPolicy(props.removalPolicy, {
applyToUpdateReplacePolicy: true,
});
this.clusterIdentifier = this.cluster.ref;
this.clusterResourceIdentifier = this.cluster.attrClusterResourceId;
const port = Token.asNumber(this.cluster.attrPort);
this.clusterEndpoint = new Endpoint(this.cluster.attrEndpoint, port);
this.clusterReadEndpoint = new Endpoint(this.cluster.attrReadEndpoint, port);
this.setLogRetention(this, props, enableCloudwatchLogsExports);
if (secret) {
this.secret = secret.attach(this);
}
// Create the instances
const instanceCount = props.instances ?? DatabaseCluster.DEFAULT_NUM_INSTANCES;
if (instanceCount < 1) {
throw new ValidationError('At least one instance is required', this);
}
const instanceRemovalPolicy = this.getInstanceRemovalPolicy(props);
const caCertificateIdentifier = props.caCertificate ? props.caCertificate.toString() : undefined;
for (let i = 0; i < instanceCount; i++) {
const instanceIndex = i + 1;
const instanceIdentifier = props.instanceIdentifierBase != null ? `${props.instanceIdentifierBase}${instanceIndex}`
: props.dbClusterName != null ? `${props.dbClusterName}instance${instanceIndex}` : undefined;
const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, {
// Link to cluster
dbClusterIdentifier: this.cluster.ref,
dbInstanceIdentifier: instanceIdentifier,
// Instance properties
dbInstanceClass: databaseInstanceType(props.instanceType),
enablePerformanceInsights: props.enablePerformanceInsights,
caCertificateIdentifier: caCertificateIdentifier,
});
instance.applyRemovalPolicy(instanceRemovalPolicy, {
applyToUpdateReplacePolicy: true,
});
// We must have a dependency on the NAT gateway provider here to create
// things in the right order.
instance.node.addDependency(internetConnectivityEstablished);
this.instanceIdentifiers.push(instance.ref);
this.instanceEndpoints.push(new Endpoint(instance.attrEndpoint, port));
}
this.connections = new ec2.Connections({
defaultPort: ec2.Port.tcp(port),
securityGroups: [securityGroup],
});
}