diff --git a/README.md b/README.md index 23c769c..cec9a73 100644 --- a/README.md +++ b/README.md @@ -58,12 +58,13 @@ $ pulumi up 1. [Project](#project) 2. [Database](#database) -3. [Redis](#redis) -4. [StaticSite](#static-site) -5. [WebServer](#web-server) -6. [Nuxt SSR](#nuxt-ssr-preset) -7. [Mongo](#mongo) -8. [EcsService](#ecs-service) +3. [Database Replica](#database-replica) +4. [Redis](#redis) +5. [StaticSite](#static-site) +6. [WebServer](#web-server) +7. [Nuxt SSR](#nuxt-ssr-preset) +8. [Mongo](#mongo) +9. [EcsService](#ecs-service) ### Project @@ -380,6 +381,45 @@ If the password is not specified it will be autogenerated. The database password is stored as a secret inside AWS Secret Manager. The secret will be available on the `Database` resource as `password.secret`. +### Database Replica + +AWS RDS Postgres instance. + +Features: + +- enabled encryption with a symmetric encryption key +- deployed inside an isolated subnet + +
+ +```ts +new DatabaseReplica(name: string, args: DatabaseReplicaArgs, opts?: pulumi.CustomResourceOptions); +``` + +| Argument | Description | +| :------- | :--------------------------------------------: | +| name \* | The unique name of the resource. | +| args \* | The arguments to resource properties. | +| opts | Bag of options to control resource's behavior. | + +```ts +type DatabaseReplicaArgs = { + replicateSourceDb: pulumi.Input; + dbSubnetGroupName: pulumi.Input; + dbSecurityGroupId: pulumi.Input; + monitoringRole?: aws.iam.Role; + multiAz?: pulumi.Input; + applyImmediately?: pulumi.Input; + allocatedStorage?: pulumi.Input; + maxAllocatedStorage?: pulumi.Input; + instanceClass?: pulumi.Input; + tags?: pulumi.Input<{ + [key: string]: pulumi.Input; + }>; +}; +``` +Database replica requires primary DB instance to exist. + ### Redis [Upstash](https://upstash.com) Redis instance. diff --git a/src/components/database-replica.ts b/src/components/database-replica.ts new file mode 100644 index 0000000..7fc4327 --- /dev/null +++ b/src/components/database-replica.ts @@ -0,0 +1,126 @@ +import * as aws from '@pulumi/aws'; +import * as pulumi from '@pulumi/pulumi'; +import { commonTags } from '../constants'; + +export type DatabaseReplicaArgs = { + /** + * ARN of the primary DB that we want to replicate. + */ + replicateSourceDb: pulumi.Input; + /** + * DB subnet group name. Should be the same as primary instance. + * * If primary DB is instance of studion:Database, it can be accessed as + * `db.dbSubnetGroup.name`. + */ + dbSubnetGroupName: pulumi.Input; + /** + * DB security group ID. Should be the same as primary instance. + * If primary DB is instance of studion:Database, it can be accessed as + * `db.dbSecurityGroup.id`. + */ + dbSecurityGroupId: pulumi.Input; + /** + * IAM Monitoring role. Should be the same as primary instance. + */ + monitoringRole?: aws.iam.Role; + /** + * Specifies if the RDS instance is multi-AZ. Defaults to false. + */ + multiAz?: pulumi.Input; + /** + * Specifies whether any database modifications are applied immediately, + * or during the next maintenance window. Default is false. + */ + applyImmediately?: pulumi.Input; + /** + * The allocated storage in gibibytes. Defaults to 20GB. + */ + allocatedStorage?: pulumi.Input; + /** + * The upper limit to which Amazon RDS can automatically scale + * the storage of the DB instance. Defaults to 100GB. + */ + maxAllocatedStorage?: pulumi.Input; + /** + * The instance type of the RDS instance. Defaults to 'db.t4g.micro'. + */ + instanceClass?: pulumi.Input; + /** + * A map of tags to assign to the resource. + */ + tags?: pulumi.Input<{ + [key: string]: pulumi.Input; + }>; +}; + +const defaults = { + multiAz: false, + applyImmediately: false, + skipFinalSnapshot: false, + allocatedStorage: 20, + maxAllocatedStorage: 100, + instanceClass: 'db.t4g.micro', + enableMonitoring: false, +}; + +export class DatabaseReplica extends pulumi.ComponentResource { + name: string; + instance: aws.rds.Instance; + monitoringRole?: aws.iam.Role; + + constructor( + name: string, + args: DatabaseReplicaArgs, + opts: pulumi.ComponentResourceOptions = {}, + ) { + super('studion:DatabaseReplica', name, {}, opts); + + this.name = name; + + const argsWithDefaults = Object.assign({}, defaults, args); + this.monitoringRole = argsWithDefaults.monitoringRole; + this.instance = this.createDatabaseInstance(args); + + this.registerOutputs(); + } + + private createDatabaseInstance(args: DatabaseReplicaArgs) { + 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: '15.5', + allocatedStorage: argsWithDefaults.allocatedStorage, + maxAllocatedStorage: argsWithDefaults.maxAllocatedStorage, + instanceClass: argsWithDefaults.instanceClass, + dbSubnetGroupName: argsWithDefaults.dbSubnetGroupName, + vpcSecurityGroupIds: [argsWithDefaults.dbSecurityGroupId], + storageEncrypted: true, + multiAz: argsWithDefaults.multiAz, + publiclyAccessible: false, + applyImmediately: argsWithDefaults.applyImmediately, + autoMinorVersionUpgrade: true, + maintenanceWindow: 'Mon:07:00-Mon:07:30', + replicateSourceDb: argsWithDefaults.replicateSourceDb, + ...monitoringOptions, + tags: { ...commonTags, ...argsWithDefaults.tags }, + }, + { parent: this } + ); + return instance; + } +} diff --git a/src/index.ts b/src/index.ts index aaeaaec..4bc10ad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export * from './components/web-server'; export * from './components/mongo'; export * from './components/static-site'; export * from './components/database'; +export * from './components/database-replica'; export * from './components/redis'; export * from './components/project'; export * from './components/ec2-ssm-connect';