Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export class Database extends pulumi.ComponentResource {
args: DatabaseArgs,
opts: pulumi.ComponentResourceOptions = {},
) {
super('studion:Database', name, {}, opts);
super('studion:LegacyDatabase', name, {}, opts);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?


this.name = name;

Expand Down
309 changes: 309 additions & 0 deletions src/v2/components/database/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
import * as aws from '@pulumi/aws';
import * as pulumi from '@pulumi/pulumi';
import { Password } from '../../../components/password';
import { commonTags } from '../../../constants';

export namespace Database {
export type Args = {
/**
* The name of the database to create when the DB instance is created.
*/
dbName: pulumi.Input<string>;
/**
* Username for the master DB user.
*/
username: pulumi.Input<string>;
vpcId: pulumi.Input<string>;
isolatedSubnetIds: pulumi.Input<pulumi.Input<string>[]>;
/**
* The IPv4 CIDR block for the VPC.
*/
vpcCidrBlock: pulumi.Input<string>;
/**
* Specifies if the RDS instance is multi-AZ. Defaults to false.
*/
multiAz?: pulumi.Input<boolean>;
/**
* Password for the master DB user. If not specified it will be autogenerated.
* The value will be stored as a secret in AWS Secret Manager.
*/
password?: pulumi.Input<string>;
/**
* Specifies whether any database modifications are applied immediately,
* or during the next maintenance window. Default is false.
*/
applyImmediately?: pulumi.Input<boolean>;
/**
* Determines whether a final DB snapshot is created before the DB
* instance is deleted. Defaults to false.
*/
skipFinalSnapshot?: pulumi.Input<boolean>;
/**
* The allocated storage in gibibytes. Defaults to 20GB.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gibibyte, I like it. 👌 😂

On a serious note, we can drop comments where the name of the parameter is self-explanatory.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct abbreviation for gibibyte is GiB so the typo should be fixed.

Sorry for barging in, I get notified for all activity and couldn't resist on this one 😄

*/
allocatedStorage?: pulumi.Input<number>;
/**
* The upper limit to which Amazon RDS can automatically scale
* the storage of the DB instance. Defaults to 100GB.
*/
maxAllocatedStorage?: pulumi.Input<number>;
/**
* The instance type of the RDS instance. Defaults to 'db.t4g.micro'.
*/
instanceClass?: pulumi.Input<string>;
/**
* Set this to true to enable database monitoring. Defaults to false.
*/
enableMonitoring?: pulumi.Input<boolean>;
/**
* Set this to true to allow major version upgrades, for example when creating
* db from the snapshot. Defaults to false.
*/
allowMajorVersionUpgrade?: pulumi.Input<boolean>;
/**
* Set this to false to disallow automatic minor version updates to the
* DB instance during the maintenance window. Defaults to true.
*/
autoMinorVersionUpgrade?: pulumi.Input<boolean>;
/**
* The name of custom aws.rds.ParameterGroup. Setting this param will apply custom
* DB parameters to this instance.
*/
parameterGroupName?: pulumi.Input<string>;
/**
* Set this to `{ enabled: true }` to enable low-downtime updates using RDS Blue/Green deployments.
* Defaults to `{ enabled: false }`.
*/
blueGreenUpdate?: pulumi.Input<aws.types.input.rds.InstanceBlueGreenUpdate>;
/**
* Specifies whether to create this database from a snapshot.
* This correlates to the snapshot ID you'd find in the RDS console,
* e.g: rds:production-2015-06-26-06-05.
*/
snapshotIdentifier?: pulumi.Input<string>;
/**
* The DB engine version. Defaults to '17.2'.
*/
engineVersion?: pulumi.Input<string>;
/**
* A map of tags to assign to the resource.
*/
tags?: pulumi.Input<{
[key: string]: pulumi.Input<string>;
}>;
};
}

const defaults = {
multiAz: false,
applyImmediately: false,
skipFinalSnapshot: false,
allocatedStorage: 20,
maxAllocatedStorage: 100,
instanceClass: 'db.t4g.micro',
enableMonitoring: false,
allowMajorVersionUpgrade: false,
autoMinorVersionUpgrade: true,
engineVersion: '17.2',
blueGreenUpdate: {
enabled: false,
},
};

export class Database extends pulumi.ComponentResource {
name: string;
instance: aws.rds.Instance;
kms: aws.kms.Key;
dbSubnetGroup: aws.rds.SubnetGroup;
dbSecurityGroup: aws.ec2.SecurityGroup;
password: Password;
encryptedSnapshotCopy?: aws.rds.SnapshotCopy;
monitoringRole?: aws.iam.Role;

constructor(
name: string,
args: Database.Args,
opts: pulumi.ComponentResourceOptions = {},
) {
super('studion:Database', name, {}, opts);

this.name = name;

const argsWithDefaults = Object.assign({}, defaults, args);
const {
vpcId,
isolatedSubnetIds,
vpcCidrBlock,
enableMonitoring,
snapshotIdentifier,
} = argsWithDefaults;
this.dbSubnetGroup = this.createSubnetGroup({ isolatedSubnetIds });
this.dbSecurityGroup = this.createSecurityGroup({ vpcId, vpcCidrBlock });
this.kms = this.createEncryptionKey();
this.password = new Password(
`${this.name}-database-password`,
{ value: args.password },
{ parent: this },
);
if (enableMonitoring) {
this.monitoringRole = this.createMonitoringRole();
}
if (snapshotIdentifier) {
this.encryptedSnapshotCopy =
this.createEncryptedSnapshotCopy(snapshotIdentifier);
}
this.instance = this.createDatabaseInstance(args);

this.registerOutputs();
}

private createSubnetGroup({
isolatedSubnetIds,
}: Pick<Database.Args, 'isolatedSubnetIds'>) {
const dbSubnetGroup = new aws.rds.SubnetGroup(
`${this.name}-subnet-group`,
{
subnetIds: isolatedSubnetIds,
tags: commonTags,
},
{ parent: this },
);
return dbSubnetGroup;
}

private createSecurityGroup({
vpcId,
vpcCidrBlock,
}: Pick<Database.Args, 'vpcId' | 'vpcCidrBlock'>) {
const dbSecurityGroup = new aws.ec2.SecurityGroup(
`${this.name}-security-group`,
{
vpcId,
ingress: [
{
protocol: 'tcp',
fromPort: 5432,
toPort: 5432,
cidrBlocks: [vpcCidrBlock],
},
],
tags: commonTags,
},
{ parent: this },
);
return dbSecurityGroup;
}

private createEncryptionKey() {
const kms = new aws.kms.Key(
`${this.name}-rds-key`,
{
description: `${this.name} RDS encryption key`,
customerMasterKeySpec: 'SYMMETRIC_DEFAULT',
isEnabled: true,
keyUsage: 'ENCRYPT_DECRYPT',
multiRegion: false,
enableKeyRotation: true,
tags: commonTags,
},
{ parent: this },
);
return kms;
}

private createMonitoringRole() {
const monitoringRole = new aws.iam.Role(`${this.name}-rds-monitoring`, {
assumeRolePolicy: {
Version: '2012-10-17',
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'monitoring.rds.amazonaws.com',
},
},
],
},
});

new aws.iam.RolePolicyAttachment(
`${this.name}-rds-monitoring-role-attachment`,
{
role: monitoringRole.name,
policyArn:
'arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole',
},
);

return monitoringRole;
}

private createEncryptedSnapshotCopy(
snapshotIdentifier: NonNullable<Database.Args['snapshotIdentifier']>,
) {
const encryptedSnapshotCopy = new aws.rds.SnapshotCopy(
`${this.name}-encrypted-snapshot-copy`,
{
sourceDbSnapshotIdentifier: snapshotIdentifier,
targetDbSnapshotIdentifier: `${snapshotIdentifier}-encrypted-copy`,
kmsKeyId: this.kms.arn,
},
{ parent: this },
);
return encryptedSnapshotCopy;
}

private createDatabaseInstance(args: Database.Args) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid passing the whole argument bag, and instead make the parameters of each method explicit.

const argsWithDefaults = Object.assign({}, defaults, args);
const stack = pulumi.getStack();

const monitoringOptions =
argsWithDefaults.enableMonitoring && this.monitoringRole
? {
monitoringInterval: 60,
monitoringRoleArn: this.monitoringRole.arn,
performanceInsightsEnabled: true,
performanceInsightsRetentionPeriod: 7,
}
: {};

const instance = new aws.rds.Instance(
`${this.name}-rds`,
{
identifierPrefix: `${this.name}-`,
engine: 'postgres',
engineVersion: argsWithDefaults.engineVersion,
allocatedStorage: argsWithDefaults.allocatedStorage,
maxAllocatedStorage: argsWithDefaults.maxAllocatedStorage,
instanceClass: argsWithDefaults.instanceClass,
dbName: argsWithDefaults.dbName,
username: argsWithDefaults.username,
password: this.password.value,
dbSubnetGroupName: this.dbSubnetGroup.name,
vpcSecurityGroupIds: [this.dbSecurityGroup.id],
storageEncrypted: true,
kmsKeyId: this.kms.arn,
multiAz: argsWithDefaults.multiAz,
publiclyAccessible: false,
skipFinalSnapshot: argsWithDefaults.skipFinalSnapshot,
applyImmediately: argsWithDefaults.applyImmediately,
autoMinorVersionUpgrade: argsWithDefaults.autoMinorVersionUpgrade,
maintenanceWindow: 'Mon:07:00-Mon:07:30',
finalSnapshotIdentifier: `${this.name}-final-snapshot-${stack}`,
backupWindow: '06:00-06:30',
backupRetentionPeriod: 14,
caCertIdentifier: 'rds-ca-rsa2048-g1',
parameterGroupName: argsWithDefaults.parameterGroupName,
allowMajorVersionUpgrade: argsWithDefaults.allowMajorVersionUpgrade,
blueGreenUpdate: argsWithDefaults.blueGreenUpdate,
snapshotIdentifier:
this.encryptedSnapshotCopy?.targetDbSnapshotIdentifier,
...monitoringOptions,
tags: { ...commonTags, ...argsWithDefaults.tags },
},
{ parent: this, dependsOn: [this.password] },
);
return instance;
}
}
1 change: 1 addition & 0 deletions src/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { WebServerBuilder } from './components/web-server/builder';
export { WebServerLoadBalancer } from './components/web-server/load-balancer';
export { ElastiCacheRedis } from './components/redis/elasticache-redis';
export { UpstashRedis } from './components/redis/upstash-redis';
export { Database } from './components/database';

import { OtelCollectorBuilder } from './otel/builder';
import { OtelCollector } from './otel';
Expand Down