Skip to content

Commit f8b25c1

Browse files
authored
feat: DB v2 component (#82)
* Create db v2 component * Install missing package * Remove ability to create custom parameter group * Cleanup * Fix kms key id type * Cleanup * Cleanup vpc parameters * Cleanup types * Fix formatting * Cleanup * Cleanup database types * Revert allocatedStorage type to number * fix tags * Fix snapshot id
1 parent b1ef9e9 commit f8b25c1

File tree

3 files changed

+272
-0
lines changed

3 files changed

+272
-0
lines changed

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"prettier": "@studion/prettier-config",
3737
"dependencies": {
3838
"@pulumi/aws": "^6.66.3",
39+
"@pulumi/aws-native": "^1.38.0",
3940
"@pulumi/awsx": "^2.21.0",
4041
"@pulumi/pulumi": "^3.146.0",
4142
"@pulumi/random": "^4.17.0",
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
import * as aws from '@pulumi/aws';
2+
import * as awsNative from '@pulumi/aws-native';
3+
import * as awsx from '@pulumi/awsx';
4+
import * as pulumi from '@pulumi/pulumi';
5+
import { Password } from '../../../components/password';
6+
import { commonTags } from '../../../constants';
7+
8+
export namespace Database {
9+
export type Instance = {
10+
dbName?: pulumi.Input<string>;
11+
engineVersion?: pulumi.Input<string>;
12+
multiAz?: pulumi.Input<boolean>;
13+
instanceClass?: pulumi.Input<string>;
14+
allowMajorVersionUpgrade?: pulumi.Input<boolean>;
15+
autoMinorVersionUpgrade?: pulumi.Input<boolean>;
16+
};
17+
18+
export type Credentials = {
19+
username?: pulumi.Input<string>;
20+
password?: pulumi.Input<string>;
21+
};
22+
23+
export type Storage = {
24+
allocatedStorage?: pulumi.Input<number>;
25+
maxAllocatedStorage?: pulumi.Input<number>;
26+
kmsKeyId?: pulumi.Input<string>;
27+
};
28+
29+
export type Args = Instance &
30+
Credentials &
31+
Storage & {
32+
vpc: pulumi.Input<awsx.ec2.Vpc>;
33+
enableMonitoring?: pulumi.Input<boolean>;
34+
applyImmediately?: pulumi.Input<boolean>;
35+
snapshotIdentifier?: pulumi.Input<string>;
36+
parameterGroupName?: pulumi.Input<string>;
37+
tags?: pulumi.Input<{
38+
[key: string]: pulumi.Input<string>;
39+
}>;
40+
};
41+
}
42+
43+
const defaults = {
44+
multiAz: false,
45+
applyImmediately: false,
46+
allocatedStorage: 20,
47+
maxAllocatedStorage: 100,
48+
instanceClass: 'db.t4g.micro',
49+
enableMonitoring: false,
50+
allowMajorVersionUpgrade: false,
51+
autoMinorVersionUpgrade: true,
52+
engineVersion: '17.2',
53+
};
54+
55+
export class Database extends pulumi.ComponentResource {
56+
name: string;
57+
instance: awsNative.rds.DbInstance;
58+
vpc: pulumi.Output<awsx.ec2.Vpc>;
59+
dbSubnetGroup: aws.rds.SubnetGroup;
60+
dbSecurityGroup: aws.ec2.SecurityGroup;
61+
password: Password;
62+
kmsKeyId: pulumi.Output<string>;
63+
monitoringRole?: aws.iam.Role;
64+
encryptedSnapshotCopy?: aws.rds.SnapshotCopy;
65+
66+
constructor(
67+
name: string,
68+
args: Database.Args,
69+
opts: pulumi.ComponentResourceOptions = {},
70+
) {
71+
super('studion:Database', name, {}, opts);
72+
73+
this.name = name;
74+
75+
const argsWithDefaults = Object.assign({}, defaults, args);
76+
const { vpc, kmsKeyId, enableMonitoring, snapshotIdentifier } =
77+
argsWithDefaults;
78+
79+
this.vpc = pulumi.output(vpc);
80+
this.dbSubnetGroup = this.createSubnetGroup();
81+
this.dbSecurityGroup = this.createSecurityGroup();
82+
83+
this.password = new Password(
84+
`${this.name}-database-password`,
85+
{ value: args.password },
86+
{ parent: this },
87+
);
88+
89+
this.kmsKeyId = kmsKeyId
90+
? pulumi.output(kmsKeyId)
91+
: this.createEncryptionKey().arn;
92+
93+
if (enableMonitoring) {
94+
this.monitoringRole = this.createMonitoringRole();
95+
}
96+
97+
if (snapshotIdentifier) {
98+
this.encryptedSnapshotCopy =
99+
this.createEncryptedSnapshotCopy(snapshotIdentifier);
100+
}
101+
102+
this.instance = this.createDatabaseInstance(argsWithDefaults);
103+
104+
this.registerOutputs();
105+
}
106+
107+
private createSubnetGroup() {
108+
return new aws.rds.SubnetGroup(
109+
`${this.name}-subnet-group`,
110+
{
111+
subnetIds: this.vpc.isolatedSubnetIds,
112+
tags: commonTags,
113+
},
114+
{ parent: this },
115+
);
116+
}
117+
118+
private createSecurityGroup() {
119+
return new aws.ec2.SecurityGroup(
120+
`${this.name}-security-group`,
121+
{
122+
vpcId: this.vpc.vpcId,
123+
ingress: [
124+
{
125+
protocol: 'tcp',
126+
fromPort: 5432,
127+
toPort: 5432,
128+
cidrBlocks: [this.vpc.vpc.cidrBlock],
129+
},
130+
],
131+
tags: commonTags,
132+
},
133+
{ parent: this },
134+
);
135+
}
136+
137+
private createEncryptionKey() {
138+
return new aws.kms.Key(
139+
`${this.name}-rds-key`,
140+
{
141+
description: `${this.name} RDS encryption key`,
142+
customerMasterKeySpec: 'SYMMETRIC_DEFAULT',
143+
isEnabled: true,
144+
keyUsage: 'ENCRYPT_DECRYPT',
145+
multiRegion: false,
146+
enableKeyRotation: true,
147+
tags: commonTags,
148+
},
149+
{ parent: this },
150+
);
151+
}
152+
153+
private createMonitoringRole() {
154+
const monitoringRole = new aws.iam.Role(
155+
`${this.name}-rds-monitoring`,
156+
{
157+
assumeRolePolicy: {
158+
Version: '2012-10-17',
159+
Statement: [
160+
{
161+
Action: 'sts:AssumeRole',
162+
Effect: 'Allow',
163+
Principal: {
164+
Service: 'monitoring.rds.amazonaws.com',
165+
},
166+
},
167+
],
168+
},
169+
},
170+
{ parent: this },
171+
);
172+
173+
new aws.iam.RolePolicyAttachment(
174+
`${this.name}-rds-monitoring-role-attachment`,
175+
{
176+
role: monitoringRole.name,
177+
policyArn:
178+
'arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole',
179+
},
180+
{ parent: this },
181+
);
182+
183+
return monitoringRole;
184+
}
185+
186+
private createEncryptedSnapshotCopy(
187+
snapshotIdentifier: Database.Args['snapshotIdentifier'],
188+
) {
189+
const sourceDbSnapshotIdentifier = pulumi
190+
.output(snapshotIdentifier)
191+
.apply(snapshotIdentifier =>
192+
aws.rds.getSnapshot({
193+
dbSnapshotIdentifier: snapshotIdentifier,
194+
}),
195+
).dbSnapshotArn;
196+
197+
return new aws.rds.SnapshotCopy(
198+
`${this.name}-encrypted-snapshot-copy`,
199+
{
200+
sourceDbSnapshotIdentifier,
201+
targetDbSnapshotIdentifier: pulumi.interpolate`${snapshotIdentifier}-encrypted-copy`,
202+
kmsKeyId: this.kmsKeyId,
203+
},
204+
{ parent: this },
205+
);
206+
}
207+
208+
private createDatabaseInstance(args: Database.Args) {
209+
const monitoringOptions =
210+
args.enableMonitoring && this.monitoringRole
211+
? {
212+
monitoringInterval: 60,
213+
monitoringRoleArn: this.monitoringRole.arn,
214+
enablePerformanceInsights: true,
215+
performanceInsightsRetentionPeriod: 7,
216+
}
217+
: {};
218+
219+
const instance = new awsNative.rds.DbInstance(
220+
`${this.name}-rds`,
221+
{
222+
dbInstanceIdentifier: `${this.name}-db-instance`,
223+
engine: 'postgres',
224+
engineVersion: args.engineVersion,
225+
dbInstanceClass: args.instanceClass,
226+
dbName: args.dbName,
227+
masterUsername: args.username,
228+
masterUserPassword: this.password.value,
229+
dbSubnetGroupName: this.dbSubnetGroup.name,
230+
vpcSecurityGroups: [this.dbSecurityGroup.id],
231+
allocatedStorage: args.allocatedStorage?.toString(),
232+
maxAllocatedStorage: args.maxAllocatedStorage,
233+
multiAz: args.multiAz,
234+
applyImmediately: args.applyImmediately,
235+
allowMajorVersionUpgrade: args.allowMajorVersionUpgrade,
236+
autoMinorVersionUpgrade: args.autoMinorVersionUpgrade,
237+
kmsKeyId: this.kmsKeyId,
238+
storageEncrypted: true,
239+
publiclyAccessible: false,
240+
preferredMaintenanceWindow: 'Mon:07:00-Mon:07:30',
241+
preferredBackupWindow: '06:00-06:30',
242+
backupRetentionPeriod: 14,
243+
caCertificateIdentifier: 'rds-ca-rsa2048-g1',
244+
dbParameterGroupName: args.parameterGroupName,
245+
dbSnapshotIdentifier:
246+
this.encryptedSnapshotCopy?.targetDbSnapshotIdentifier,
247+
...monitoringOptions,
248+
tags: pulumi
249+
.output(args.tags)
250+
.apply(tags => [
251+
...Object.entries({ ...commonTags, ...tags }).map(
252+
([key, value]) => ({ key, value }),
253+
),
254+
]),
255+
},
256+
{ parent: this, dependsOn: [this.password] },
257+
);
258+
return instance;
259+
}
260+
}

0 commit comments

Comments
 (0)