@studion/infra-code-blocks 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -7
- package/dist/components/acm-certificate.d.ts +10 -0
- package/dist/components/acm-certificate.js +29 -0
- package/dist/components/database.d.ts +48 -0
- package/dist/components/database.js +64 -0
- package/dist/components/ec2-ssm-connect.d.ts +16 -0
- package/dist/components/ec2-ssm-connect.js +92 -0
- package/dist/components/project.d.ts +24 -15
- package/dist/components/project.js +42 -111
- package/dist/components/redis.d.ts +7 -2
- package/dist/components/redis.js +7 -3
- package/dist/components/static-site.d.ts +20 -0
- package/dist/components/static-site.js +108 -0
- package/dist/components/web-server.d.ts +86 -0
- package/dist/components/web-server.js +304 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/package.json +3 -3
- package/dist/components/ecs.d.ts +0 -33
- package/dist/components/ecs.js +0 -154
- package/dist/components/rds.d.ts +0 -20
- package/dist/components/rds.js +0 -42
package/README.md
CHANGED
|
@@ -78,14 +78,16 @@ type ProjectArgs = {
|
|
|
78
78
|
)[];
|
|
79
79
|
environment: Environment;
|
|
80
80
|
hostedZoneId?: pulumi.Input<string>;
|
|
81
|
+
enableSSMConnect?: pulumi.Input<boolean>;
|
|
81
82
|
};
|
|
82
83
|
```
|
|
83
84
|
|
|
84
|
-
| Argument
|
|
85
|
-
|
|
|
86
|
-
| services \*
|
|
87
|
-
| environment \*
|
|
88
|
-
| hostedZoneId
|
|
85
|
+
| Argument | Description |
|
|
86
|
+
| :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
|
87
|
+
| services \* | Service list. |
|
|
88
|
+
| environment \* | Environment name. |
|
|
89
|
+
| hostedZoneId | Route53 hosted zone ID responsible for managing records for the domain. |
|
|
90
|
+
| enableSSMConnect | Setup ec2 instance and SSM in order to connect to the database in the private subnet. Please refer to the [SSM Connect](#ssm-connect) section for more info. |
|
|
89
91
|
|
|
90
92
|
```ts
|
|
91
93
|
type DatabaseService = {
|
|
@@ -282,9 +284,90 @@ export type WebServerArgs = {
|
|
|
282
284
|
};
|
|
283
285
|
```
|
|
284
286
|
|
|
287
|
+
## SSM Connect
|
|
288
|
+
|
|
289
|
+
The [Database](#database) component deploys a database instance inside a private subnet,
|
|
290
|
+
and it's not publicly accessible from outside of VPC.
|
|
291
|
+
<br>
|
|
292
|
+
In order to connect to the database we need to deploy the ec2 instance which will be used
|
|
293
|
+
to open an SSH tunnel to the database instance.
|
|
294
|
+
<br>
|
|
295
|
+
Because of security reasons, ec2 instance is also deployed inside private subnet
|
|
296
|
+
which means we can't directly connect to it. For that purpose, we use AWS System Manager
|
|
297
|
+
which enables us to connect to the ec2 instance even though it's inside private subnet.
|
|
298
|
+
|
|
299
|
+

|
|
300
|
+
|
|
301
|
+
**Prerequisites**
|
|
302
|
+
|
|
303
|
+
1. Install the [Session Manager plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-macos-overview.html#install-plugin-macos)
|
|
304
|
+
2. Generate a new ssh key pair or use the existing one.
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
$ ssh-keygen -f my_rsa
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
3. Set stack config property by running:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
$ pulumi config set ssh:publicKey "ssh-rsa Z...9= mymac@Studions-MBP.localdomain"
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
SSM Connect can be enabled by setting `enableSSMConnect` property to `true`.
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
const project = new studion.Project('demo-project', {
|
|
320
|
+
enableSSMConnect: true,
|
|
321
|
+
...
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
export const ec2InstanceId = project.ec2SSMConnect?.ec2.id;
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Open up your terminal and run the following command:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
$ aws ssm start-session --target EC2_INSTANCE_ID --document-name AWS-StartPortForwardingSession --parameters '{"portNumber":["22"], "localPortNumber":["9999"]}'
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Where `EC2_INSTANCE_ID` is an ID of the EC2 instance that is created for you. ID can be
|
|
334
|
+
obtained by exporting it from the stack.
|
|
335
|
+
|
|
336
|
+
Next, open another terminal window and run the following command:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
$ ssh ec2-user@localhost -p 9999 -N -L 5555:DATABASE_ADDRESS:DATABASE_PORT -i SSH_PRIVATE_KEY
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Where `DATABASE_ADDRESS` and `DATABASE_PORT` are the address and port of the database instance,
|
|
343
|
+
and `SSH_PRIVATE_KEY` is the path to the SSH private key.
|
|
344
|
+
|
|
345
|
+
And that is it! 🥳
|
|
346
|
+
Now you can use your favorite database client to connect to the database.
|
|
347
|
+
|
|
348
|
+

|
|
349
|
+
|
|
350
|
+
It is important that for the host you set `localhost` and for the port you set `5555`
|
|
351
|
+
because we have an SSH tunnel open that forwards traffic from localhost:5555 to the
|
|
352
|
+
DATABASE_ADDRESS:DATABASE_PORT. For the user, password, and database field, set values
|
|
353
|
+
which are set in the `Project`.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
const project = new studion.Project('demo-project', {
|
|
357
|
+
enableSSMConnect: true,
|
|
358
|
+
services: [
|
|
359
|
+
{
|
|
360
|
+
type: 'DATABASE',
|
|
361
|
+
dbName: 'database_name',
|
|
362
|
+
username: 'username',
|
|
363
|
+
password: 'password',
|
|
364
|
+
...
|
|
365
|
+
}
|
|
366
|
+
]
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
285
370
|
## 🚧 TODO
|
|
286
371
|
|
|
287
|
-
- [x] Allow connection with RDS via ec2 instance
|
|
288
|
-
- [x] Execute commands from ecs service
|
|
289
372
|
- [ ] Add worker service for executing tasks
|
|
290
373
|
- [ ] Update docs, describe each service, describe required stack configs...
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as pulumi from '@pulumi/pulumi';
|
|
2
|
+
import * as aws from '@pulumi/aws';
|
|
3
|
+
export type AcmCertificateArgs = {
|
|
4
|
+
domain: pulumi.Input<string>;
|
|
5
|
+
hostedZoneId: pulumi.Input<string>;
|
|
6
|
+
};
|
|
7
|
+
export declare class AcmCertificate extends pulumi.ComponentResource {
|
|
8
|
+
certificate: aws.acm.Certificate;
|
|
9
|
+
constructor(name: string, args: AcmCertificateArgs, opts?: pulumi.ComponentResourceOptions);
|
|
10
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AcmCertificate = void 0;
|
|
4
|
+
const pulumi = require("@pulumi/pulumi");
|
|
5
|
+
const aws = require("@pulumi/aws");
|
|
6
|
+
class AcmCertificate extends pulumi.ComponentResource {
|
|
7
|
+
constructor(name, args, opts = {}) {
|
|
8
|
+
super('studion:acm:Certificate', name, {}, opts);
|
|
9
|
+
this.certificate = new aws.acm.Certificate(`${args.domain}-certificate`, { domainName: args.domain, validationMethod: 'DNS' }, { parent: this });
|
|
10
|
+
const certificateValidationDomain = new aws.route53.Record(`${args.domain}-cert-validation-domain`, {
|
|
11
|
+
name: this.certificate.domainValidationOptions[0].resourceRecordName,
|
|
12
|
+
type: this.certificate.domainValidationOptions[0].resourceRecordType,
|
|
13
|
+
zoneId: args.hostedZoneId,
|
|
14
|
+
records: [
|
|
15
|
+
this.certificate.domainValidationOptions[0].resourceRecordValue,
|
|
16
|
+
],
|
|
17
|
+
ttl: 600,
|
|
18
|
+
}, {
|
|
19
|
+
parent: this,
|
|
20
|
+
deleteBeforeReplace: true,
|
|
21
|
+
});
|
|
22
|
+
const certificateValidation = new aws.acm.CertificateValidation(`${args.domain}-cert-validation`, {
|
|
23
|
+
certificateArn: this.certificate.arn,
|
|
24
|
+
validationRecordFqdns: [certificateValidationDomain.fqdn],
|
|
25
|
+
}, { parent: this });
|
|
26
|
+
this.registerOutputs();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.AcmCertificate = AcmCertificate;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as aws from '@pulumi/aws';
|
|
2
|
+
import * as awsx from '@pulumi/awsx';
|
|
3
|
+
import * as pulumi from '@pulumi/pulumi';
|
|
4
|
+
export type DatabaseArgs = {
|
|
5
|
+
/**
|
|
6
|
+
* The name of the database to create when the DB instance is created.
|
|
7
|
+
*/
|
|
8
|
+
dbName: pulumi.Input<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Username for the master DB user.
|
|
11
|
+
*/
|
|
12
|
+
username: pulumi.Input<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Password for the master DB user.
|
|
15
|
+
*/
|
|
16
|
+
password: pulumi.Input<string>;
|
|
17
|
+
/**
|
|
18
|
+
* The awsx.ec2.Vpc resource.
|
|
19
|
+
*/
|
|
20
|
+
vpc: awsx.ec2.Vpc;
|
|
21
|
+
/**
|
|
22
|
+
* Specifies whether any database modifications are applied immediately, or during the next maintenance window. Default is false.
|
|
23
|
+
*/
|
|
24
|
+
applyImmediately?: pulumi.Input<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Determines whether a final DB snapshot is created before the DB instance is deleted.
|
|
27
|
+
*/
|
|
28
|
+
skipFinalSnapshot?: pulumi.Input<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* The allocated storage in gibibytes.
|
|
31
|
+
*/
|
|
32
|
+
allocatedStorage?: pulumi.Input<number>;
|
|
33
|
+
/**
|
|
34
|
+
* The upper limit to which Amazon RDS can automatically scale the storage of the DB instance.
|
|
35
|
+
*/
|
|
36
|
+
maxAllocatedStorage?: pulumi.Input<number>;
|
|
37
|
+
/**
|
|
38
|
+
* The instance type of the RDS instance.
|
|
39
|
+
*/
|
|
40
|
+
instanceClass?: pulumi.Input<string>;
|
|
41
|
+
};
|
|
42
|
+
export declare class Database extends pulumi.ComponentResource {
|
|
43
|
+
instance: aws.rds.Instance;
|
|
44
|
+
kms: aws.kms.Key;
|
|
45
|
+
dbSubnetGroup: aws.rds.SubnetGroup;
|
|
46
|
+
dbSecurityGroup: aws.ec2.SecurityGroup;
|
|
47
|
+
constructor(name: string, args: DatabaseArgs, opts?: pulumi.ComponentResourceOptions);
|
|
48
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Database = void 0;
|
|
4
|
+
const aws = require("@pulumi/aws");
|
|
5
|
+
const pulumi = require("@pulumi/pulumi");
|
|
6
|
+
const defaults = {
|
|
7
|
+
applyImmediately: false,
|
|
8
|
+
skipFinalSnapshot: false,
|
|
9
|
+
allocatedStorage: 20,
|
|
10
|
+
maxAllocatedStorage: 100,
|
|
11
|
+
instanceClass: 'db.t3.micro',
|
|
12
|
+
};
|
|
13
|
+
class Database extends pulumi.ComponentResource {
|
|
14
|
+
constructor(name, args, opts = {}) {
|
|
15
|
+
super('studion:Database', name, {}, opts);
|
|
16
|
+
const argsWithDefaults = Object.assign({}, defaults, args);
|
|
17
|
+
this.dbSubnetGroup = new aws.rds.SubnetGroup(`${name}-subnet-group`, {
|
|
18
|
+
subnetIds: argsWithDefaults.vpc.privateSubnetIds,
|
|
19
|
+
}, { parent: this });
|
|
20
|
+
this.dbSecurityGroup = new aws.ec2.SecurityGroup(`${name}-security-group`, {
|
|
21
|
+
vpcId: argsWithDefaults.vpc.vpcId,
|
|
22
|
+
ingress: [
|
|
23
|
+
{
|
|
24
|
+
protocol: 'tcp',
|
|
25
|
+
fromPort: 5432,
|
|
26
|
+
toPort: 5432,
|
|
27
|
+
cidrBlocks: [argsWithDefaults.vpc.vpc.cidrBlock],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}, { parent: this });
|
|
31
|
+
this.kms = new aws.kms.Key(`${name}-rds-key`, {
|
|
32
|
+
customerMasterKeySpec: 'SYMMETRIC_DEFAULT',
|
|
33
|
+
isEnabled: true,
|
|
34
|
+
keyUsage: 'ENCRYPT_DECRYPT',
|
|
35
|
+
multiRegion: false,
|
|
36
|
+
enableKeyRotation: true,
|
|
37
|
+
}, { parent: this });
|
|
38
|
+
this.instance = new aws.rds.Instance(`${name}-rds`, {
|
|
39
|
+
identifier: name,
|
|
40
|
+
engine: 'postgres',
|
|
41
|
+
engineVersion: '14.9',
|
|
42
|
+
allocatedStorage: argsWithDefaults.allocatedStorage,
|
|
43
|
+
maxAllocatedStorage: argsWithDefaults.maxAllocatedStorage,
|
|
44
|
+
instanceClass: argsWithDefaults.instanceClass,
|
|
45
|
+
dbName: argsWithDefaults.dbName,
|
|
46
|
+
username: argsWithDefaults.username,
|
|
47
|
+
password: argsWithDefaults.password,
|
|
48
|
+
dbSubnetGroupName: this.dbSubnetGroup.name,
|
|
49
|
+
vpcSecurityGroupIds: [this.dbSecurityGroup.id],
|
|
50
|
+
storageEncrypted: true,
|
|
51
|
+
kmsKeyId: this.kms.arn,
|
|
52
|
+
publiclyAccessible: false,
|
|
53
|
+
skipFinalSnapshot: argsWithDefaults.skipFinalSnapshot,
|
|
54
|
+
applyImmediately: argsWithDefaults.applyImmediately,
|
|
55
|
+
autoMinorVersionUpgrade: true,
|
|
56
|
+
maintenanceWindow: 'Mon:07:00-Mon:07:30',
|
|
57
|
+
finalSnapshotIdentifier: `${name}-final-snapshot`,
|
|
58
|
+
backupWindow: '06:00-06:30',
|
|
59
|
+
backupRetentionPeriod: 14,
|
|
60
|
+
}, { parent: this });
|
|
61
|
+
this.registerOutputs();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.Database = Database;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as pulumi from '@pulumi/pulumi';
|
|
2
|
+
import * as aws from '@pulumi/aws';
|
|
3
|
+
import * as awsx from '@pulumi/awsx';
|
|
4
|
+
export type Ec2SSMConnectArgs = {
|
|
5
|
+
vpc: awsx.ec2.Vpc;
|
|
6
|
+
sshPublicKey: pulumi.Input<string>;
|
|
7
|
+
};
|
|
8
|
+
export declare class Ec2SSMConnect extends pulumi.ComponentResource {
|
|
9
|
+
ec2SecurityGroup: aws.ec2.SecurityGroup;
|
|
10
|
+
ssmVpcEndpoint: aws.ec2.VpcEndpoint;
|
|
11
|
+
ec2MessagesVpcEndpoint: aws.ec2.VpcEndpoint;
|
|
12
|
+
ssmMessagesVpcEndpoint: aws.ec2.VpcEndpoint;
|
|
13
|
+
ec2: aws.ec2.Instance;
|
|
14
|
+
sshKeyPair: aws.ec2.KeyPair;
|
|
15
|
+
constructor(name: string, args: Ec2SSMConnectArgs, opts?: pulumi.ComponentResourceOptions);
|
|
16
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Ec2SSMConnect = void 0;
|
|
4
|
+
const pulumi = require("@pulumi/pulumi");
|
|
5
|
+
const aws = require("@pulumi/aws");
|
|
6
|
+
const config = new pulumi.Config('aws');
|
|
7
|
+
const awsRegion = config.require('region');
|
|
8
|
+
class Ec2SSMConnect extends pulumi.ComponentResource {
|
|
9
|
+
constructor(name, args, opts = {}) {
|
|
10
|
+
super('studion:Ec2BastionSSMConnect', name, {}, opts);
|
|
11
|
+
const subnetId = args.vpc.privateSubnetIds.apply(ids => ids[0]);
|
|
12
|
+
this.ec2SecurityGroup = new aws.ec2.SecurityGroup(`${name}-ec2-security-group`, {
|
|
13
|
+
ingress: [
|
|
14
|
+
{
|
|
15
|
+
protocol: 'tcp',
|
|
16
|
+
fromPort: 22,
|
|
17
|
+
toPort: 22,
|
|
18
|
+
cidrBlocks: ['0.0.0.0/0'],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
egress: [
|
|
22
|
+
{ protocol: '-1', fromPort: 0, toPort: 0, cidrBlocks: ['0.0.0.0/0'] },
|
|
23
|
+
],
|
|
24
|
+
vpcId: args.vpc.vpcId,
|
|
25
|
+
}, { parent: this });
|
|
26
|
+
const role = new aws.iam.Role(`${name}-ec2-role`, {
|
|
27
|
+
assumeRolePolicy: {
|
|
28
|
+
Version: '2012-10-17',
|
|
29
|
+
Statement: [
|
|
30
|
+
{
|
|
31
|
+
Effect: 'Allow',
|
|
32
|
+
Principal: {
|
|
33
|
+
Service: 'ec2.amazonaws.com',
|
|
34
|
+
},
|
|
35
|
+
Action: 'sts:AssumeRole',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
}, { parent: this });
|
|
40
|
+
const ssmPolicyAttachment = new aws.iam.RolePolicyAttachment(`${name}-ssm-policy-attachment`, {
|
|
41
|
+
role: role.name,
|
|
42
|
+
policyArn: 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore',
|
|
43
|
+
}, { parent: this });
|
|
44
|
+
const ssmProfile = new aws.iam.InstanceProfile(`${name}-ssm-profile`, {
|
|
45
|
+
role: role.name,
|
|
46
|
+
}, { parent: this, dependsOn: [ssmPolicyAttachment] });
|
|
47
|
+
this.ssmVpcEndpoint = new aws.ec2.VpcEndpoint(`${name}-ssm-vpc-endpoint`, {
|
|
48
|
+
vpcId: args.vpc.vpcId,
|
|
49
|
+
ipAddressType: 'ipv4',
|
|
50
|
+
serviceName: `com.amazonaws.${awsRegion}.ssm`,
|
|
51
|
+
vpcEndpointType: 'Interface',
|
|
52
|
+
subnetIds: [subnetId],
|
|
53
|
+
securityGroupIds: [this.ec2SecurityGroup.id],
|
|
54
|
+
privateDnsEnabled: true,
|
|
55
|
+
}, { parent: this });
|
|
56
|
+
this.ec2MessagesVpcEndpoint = new aws.ec2.VpcEndpoint(`${name}-ec2messages-vpc-endpoint`, {
|
|
57
|
+
vpcId: args.vpc.vpcId,
|
|
58
|
+
ipAddressType: 'ipv4',
|
|
59
|
+
serviceName: `com.amazonaws.${awsRegion}.ec2messages`,
|
|
60
|
+
vpcEndpointType: 'Interface',
|
|
61
|
+
subnetIds: [subnetId],
|
|
62
|
+
securityGroupIds: [this.ec2SecurityGroup.id],
|
|
63
|
+
privateDnsEnabled: true,
|
|
64
|
+
}, { parent: this });
|
|
65
|
+
this.ssmMessagesVpcEndpoint = new aws.ec2.VpcEndpoint(`${name}-ssmmessages-vpc-endpoint`, {
|
|
66
|
+
vpcId: args.vpc.vpcId,
|
|
67
|
+
ipAddressType: 'ipv4',
|
|
68
|
+
serviceName: `com.amazonaws.${awsRegion}.ssmmessages`,
|
|
69
|
+
vpcEndpointType: 'Interface',
|
|
70
|
+
subnetIds: [subnetId],
|
|
71
|
+
securityGroupIds: [this.ec2SecurityGroup.id],
|
|
72
|
+
privateDnsEnabled: true,
|
|
73
|
+
}, { parent: this });
|
|
74
|
+
this.sshKeyPair = new aws.ec2.KeyPair(`${name}-ec2-keypair`, {
|
|
75
|
+
publicKey: args.sshPublicKey,
|
|
76
|
+
}, { parent: this });
|
|
77
|
+
this.ec2 = new aws.ec2.Instance(`${name}-ec2`, {
|
|
78
|
+
ami: 'ami-067d1e60475437da2',
|
|
79
|
+
associatePublicIpAddress: false,
|
|
80
|
+
instanceType: 't2.micro',
|
|
81
|
+
keyName: this.sshKeyPair.keyName,
|
|
82
|
+
iamInstanceProfile: ssmProfile.name,
|
|
83
|
+
subnetId,
|
|
84
|
+
vpcSecurityGroupIds: [this.ec2SecurityGroup.id],
|
|
85
|
+
tags: {
|
|
86
|
+
Name: `${name}-ec2`,
|
|
87
|
+
},
|
|
88
|
+
}, { parent: this });
|
|
89
|
+
this.registerOutputs();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.Ec2SSMConnect = Ec2SSMConnect;
|
|
@@ -2,51 +2,60 @@ import * as pulumi from '@pulumi/pulumi';
|
|
|
2
2
|
import * as aws from '@pulumi/aws';
|
|
3
3
|
import * as awsx from '@pulumi/awsx';
|
|
4
4
|
import * as upstash from '@upstash/pulumi';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { Database, DatabaseArgs } from './database';
|
|
6
|
+
import { WebServer, WebServerArgs } from './web-server';
|
|
7
7
|
import { Redis, RedisArgs } from './redis';
|
|
8
|
+
import { StaticSite, StaticSiteArgs } from './static-site';
|
|
8
9
|
import { Environment } from '../constants';
|
|
9
|
-
|
|
10
|
+
import { Ec2SSMConnect } from './ec2-ssm-connect';
|
|
11
|
+
export type Service = Database | Redis | StaticSite | WebServer;
|
|
10
12
|
export type Services = Record<string, Service>;
|
|
11
13
|
type ServiceArgs = {
|
|
14
|
+
/**
|
|
15
|
+
* The unique name for the service.
|
|
16
|
+
*/
|
|
12
17
|
serviceName: string;
|
|
13
18
|
};
|
|
14
19
|
export type DatabaseService = {
|
|
15
20
|
type: 'DATABASE';
|
|
16
|
-
} & ServiceArgs &
|
|
21
|
+
} & ServiceArgs & Omit<DatabaseArgs, 'vpc'>;
|
|
17
22
|
export type RedisService = {
|
|
18
23
|
type: 'REDIS';
|
|
19
24
|
} & ServiceArgs & Pick<RedisArgs, 'dbName' | 'region'>;
|
|
25
|
+
export type StaticSiteService = {
|
|
26
|
+
type: 'STATIC_SITE';
|
|
27
|
+
} & ServiceArgs & Omit<StaticSiteArgs, 'hostedZoneId'>;
|
|
20
28
|
export type WebServerService = {
|
|
21
29
|
type: 'WEB_SERVER';
|
|
22
30
|
environment?: aws.ecs.KeyValuePair[] | ((services: Services) => aws.ecs.KeyValuePair[]);
|
|
23
|
-
|
|
24
|
-
} & ServiceArgs & Pick<EcsServiceArgs, 'image' | 'port' | 'desiredCount' | 'minCount' | 'maxCount' | 'size'>;
|
|
31
|
+
} & ServiceArgs & Omit<WebServerArgs, 'cluster' | 'vpc' | 'hostedZoneId' | 'environment'>;
|
|
25
32
|
export type Environment = (typeof Environment)[keyof typeof Environment];
|
|
26
33
|
export type ProjectArgs = {
|
|
27
|
-
services: (DatabaseService | RedisService | WebServerService)[];
|
|
34
|
+
services: (DatabaseService | RedisService | StaticSiteService | WebServerService)[];
|
|
28
35
|
environment: Environment;
|
|
36
|
+
hostedZoneId?: pulumi.Input<string>;
|
|
37
|
+
enableSSMConnect?: pulumi.Input<boolean>;
|
|
29
38
|
};
|
|
39
|
+
export declare class MissingHostedZoneId extends Error {
|
|
40
|
+
constructor(serviceType: string);
|
|
41
|
+
}
|
|
30
42
|
export declare class Project extends pulumi.ComponentResource {
|
|
31
43
|
name: string;
|
|
32
44
|
environment: Environment;
|
|
33
45
|
vpc: awsx.ec2.Vpc;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
lb: aws.lb.LoadBalancer | null;
|
|
39
|
-
ecsServiceSecurityGroup: aws.ec2.SecurityGroup | null;
|
|
40
|
-
upstashProvider: upstash.Provider | null;
|
|
46
|
+
cluster?: aws.ecs.Cluster;
|
|
47
|
+
hostedZoneId?: pulumi.Input<string>;
|
|
48
|
+
upstashProvider?: upstash.Provider;
|
|
49
|
+
ec2SSMConnect?: Ec2SSMConnect;
|
|
41
50
|
services: Services;
|
|
42
51
|
constructor(name: string, args: ProjectArgs, opts?: pulumi.ComponentResourceOptions);
|
|
43
52
|
private createVpc;
|
|
44
53
|
private createServices;
|
|
45
|
-
private createDatabasePrerequisites;
|
|
46
54
|
private createRedisPrerequisites;
|
|
47
55
|
private createWebServerPrerequisites;
|
|
48
56
|
private createDatabaseService;
|
|
49
57
|
private createRedisService;
|
|
58
|
+
private createStaticSiteService;
|
|
50
59
|
private createWebServerService;
|
|
51
60
|
}
|
|
52
61
|
export {};
|
|
@@ -11,30 +11,41 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
return t;
|
|
12
12
|
};
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.Project = void 0;
|
|
14
|
+
exports.Project = exports.MissingHostedZoneId = void 0;
|
|
15
15
|
const pulumi = require("@pulumi/pulumi");
|
|
16
16
|
const aws = require("@pulumi/aws");
|
|
17
17
|
const awsx = require("@pulumi/awsx");
|
|
18
18
|
const upstash = require("@upstash/pulumi");
|
|
19
|
-
const
|
|
20
|
-
const
|
|
19
|
+
const database_1 = require("./database");
|
|
20
|
+
const web_server_1 = require("./web-server");
|
|
21
21
|
const redis_1 = require("./redis");
|
|
22
|
+
const static_site_1 = require("./static-site");
|
|
23
|
+
const ec2_ssm_connect_1 = require("./ec2-ssm-connect");
|
|
24
|
+
class MissingHostedZoneId extends Error {
|
|
25
|
+
constructor(serviceType) {
|
|
26
|
+
super(`Project::hostedZoneId argument must be provided
|
|
27
|
+
in order to create ${serviceType} service`);
|
|
28
|
+
this.name = this.constructor.name;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.MissingHostedZoneId = MissingHostedZoneId;
|
|
22
32
|
class Project extends pulumi.ComponentResource {
|
|
23
33
|
constructor(name, args, opts = {}) {
|
|
24
34
|
super('studion:Project', name, {}, opts);
|
|
25
|
-
this.dbSubnetGroup = null;
|
|
26
|
-
this.dbSecurityGroup = null;
|
|
27
|
-
this.cluster = null;
|
|
28
|
-
this.lbSecurityGroup = null;
|
|
29
|
-
this.lb = null;
|
|
30
|
-
this.ecsServiceSecurityGroup = null;
|
|
31
|
-
this.upstashProvider = null;
|
|
32
35
|
this.services = {};
|
|
33
|
-
const { services, environment } = args;
|
|
36
|
+
const { services, environment, hostedZoneId } = args;
|
|
34
37
|
this.name = name;
|
|
38
|
+
this.hostedZoneId = hostedZoneId;
|
|
35
39
|
this.environment = environment;
|
|
36
40
|
this.vpc = this.createVpc();
|
|
37
41
|
this.createServices(services);
|
|
42
|
+
if (args.enableSSMConnect) {
|
|
43
|
+
const sshConfig = new pulumi.Config('ssh');
|
|
44
|
+
this.ec2SSMConnect = new ec2_ssm_connect_1.Ec2SSMConnect(`${name}-ssm-connect`, {
|
|
45
|
+
vpc: this.vpc,
|
|
46
|
+
sshPublicKey: sshConfig.require('publicKey'),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
38
49
|
this.registerOutputs();
|
|
39
50
|
}
|
|
40
51
|
createVpc() {
|
|
@@ -46,11 +57,8 @@ class Project extends pulumi.ComponentResource {
|
|
|
46
57
|
return vpc;
|
|
47
58
|
}
|
|
48
59
|
createServices(services) {
|
|
49
|
-
const hasDatabaseService = services.some(it => it.type === 'DATABASE');
|
|
50
60
|
const hasRedisService = services.some(it => it.type === 'REDIS');
|
|
51
61
|
const hasWebServerService = services.some(it => it.type === 'WEB_SERVER');
|
|
52
|
-
if (hasDatabaseService)
|
|
53
|
-
this.createDatabasePrerequisites();
|
|
54
62
|
if (hasRedisService)
|
|
55
63
|
this.createRedisPrerequisites();
|
|
56
64
|
if (hasWebServerService)
|
|
@@ -60,26 +68,12 @@ class Project extends pulumi.ComponentResource {
|
|
|
60
68
|
this.createDatabaseService(it);
|
|
61
69
|
if (it.type === 'REDIS')
|
|
62
70
|
this.createRedisService(it);
|
|
71
|
+
if (it.type === 'STATIC_SITE')
|
|
72
|
+
this.createStaticSiteService(it);
|
|
63
73
|
if (it.type === 'WEB_SERVER')
|
|
64
74
|
this.createWebServerService(it);
|
|
65
75
|
});
|
|
66
76
|
}
|
|
67
|
-
createDatabasePrerequisites() {
|
|
68
|
-
this.dbSubnetGroup = new aws.rds.SubnetGroup('db-subnet-group', {
|
|
69
|
-
subnetIds: this.vpc.privateSubnetIds,
|
|
70
|
-
}, { parent: this });
|
|
71
|
-
this.dbSecurityGroup = new aws.ec2.SecurityGroup('db-security-group', {
|
|
72
|
-
vpcId: this.vpc.vpcId,
|
|
73
|
-
ingress: [
|
|
74
|
-
{
|
|
75
|
-
protocol: 'tcp',
|
|
76
|
-
fromPort: 5432,
|
|
77
|
-
toPort: 5432,
|
|
78
|
-
cidrBlocks: [this.vpc.vpc.cidrBlock],
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
}, { parent: this });
|
|
82
|
-
}
|
|
83
77
|
createRedisPrerequisites() {
|
|
84
78
|
const upstashConfig = new pulumi.Config('upstash');
|
|
85
79
|
this.upstashProvider = new upstash.Provider('upstash', {
|
|
@@ -89,103 +83,40 @@ class Project extends pulumi.ComponentResource {
|
|
|
89
83
|
}
|
|
90
84
|
createWebServerPrerequisites() {
|
|
91
85
|
this.cluster = new aws.ecs.Cluster(`${this.name}-cluster`, { name: this.name }, { parent: this });
|
|
92
|
-
this.lbSecurityGroup = new aws.ec2.SecurityGroup('ecs-lb-security-group', {
|
|
93
|
-
vpcId: this.vpc.vpcId,
|
|
94
|
-
ingress: [
|
|
95
|
-
{
|
|
96
|
-
fromPort: 80,
|
|
97
|
-
toPort: 80,
|
|
98
|
-
protocol: 'tcp',
|
|
99
|
-
cidrBlocks: ['0.0.0.0/0'],
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
egress: [
|
|
103
|
-
{
|
|
104
|
-
fromPort: 0,
|
|
105
|
-
toPort: 0,
|
|
106
|
-
protocol: '-1',
|
|
107
|
-
cidrBlocks: ['0.0.0.0/0'],
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
}, { parent: this });
|
|
111
|
-
this.lb = new aws.lb.LoadBalancer('ecs-load-balancer', {
|
|
112
|
-
loadBalancerType: 'application',
|
|
113
|
-
subnets: this.vpc.publicSubnetIds,
|
|
114
|
-
securityGroups: [this.lbSecurityGroup.id],
|
|
115
|
-
internal: false,
|
|
116
|
-
ipAddressType: 'ipv4',
|
|
117
|
-
}, { parent: this });
|
|
118
|
-
this.ecsServiceSecurityGroup = new aws.ec2.SecurityGroup('ecs-service-security-group', {
|
|
119
|
-
vpcId: this.vpc.vpcId,
|
|
120
|
-
ingress: [
|
|
121
|
-
{
|
|
122
|
-
fromPort: 0,
|
|
123
|
-
toPort: 0,
|
|
124
|
-
protocol: '-1',
|
|
125
|
-
securityGroups: [this.lbSecurityGroup.id],
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
egress: [
|
|
129
|
-
{
|
|
130
|
-
fromPort: 0,
|
|
131
|
-
toPort: 0,
|
|
132
|
-
protocol: '-1',
|
|
133
|
-
cidrBlocks: ['0.0.0.0/0'],
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
}, { parent: this });
|
|
137
86
|
}
|
|
138
87
|
createDatabaseService(options) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const instance = new rds_1.Rds(serviceName, Object.assign(Object.assign({}, rdsOptions), { subnetGroupName: this.dbSubnetGroup.name, securityGroupIds: [this.dbSecurityGroup.id] }), { parent: this });
|
|
143
|
-
this.services[serviceName] = instance;
|
|
88
|
+
const { serviceName, type } = options, databaseOptions = __rest(options, ["serviceName", "type"]);
|
|
89
|
+
const service = new database_1.Database(serviceName, Object.assign(Object.assign({}, databaseOptions), { vpc: this.vpc }), { parent: this });
|
|
90
|
+
this.services[serviceName] = service;
|
|
144
91
|
}
|
|
145
92
|
createRedisService(options) {
|
|
146
93
|
if (!this.upstashProvider)
|
|
147
94
|
return;
|
|
148
95
|
const { serviceName } = options, redisOptions = __rest(options, ["serviceName"]);
|
|
149
|
-
const
|
|
96
|
+
const service = new redis_1.Redis(serviceName, redisOptions, {
|
|
150
97
|
parent: this,
|
|
151
98
|
provider: this.upstashProvider,
|
|
152
99
|
});
|
|
153
|
-
this.services[options.serviceName] =
|
|
100
|
+
this.services[options.serviceName] = service;
|
|
101
|
+
}
|
|
102
|
+
createStaticSiteService(options) {
|
|
103
|
+
const { serviceName } = options, staticSiteOptions = __rest(options, ["serviceName"]);
|
|
104
|
+
if (!this.hostedZoneId)
|
|
105
|
+
throw new MissingHostedZoneId(options.type);
|
|
106
|
+
const service = new static_site_1.StaticSite(serviceName, Object.assign(Object.assign({}, staticSiteOptions), { hostedZoneId: this.hostedZoneId }), { parent: this });
|
|
107
|
+
this.services[serviceName] = service;
|
|
154
108
|
}
|
|
155
109
|
createWebServerService(options) {
|
|
156
|
-
if (!this.cluster
|
|
110
|
+
if (!this.cluster)
|
|
157
111
|
return;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
port: ecsOptions.port,
|
|
162
|
-
protocol: 'HTTP',
|
|
163
|
-
targetType: 'ip',
|
|
164
|
-
vpcId: this.vpc.vpcId,
|
|
165
|
-
healthCheck: {
|
|
166
|
-
healthyThreshold: 3,
|
|
167
|
-
unhealthyThreshold: 2,
|
|
168
|
-
interval: 60,
|
|
169
|
-
timeout: 5,
|
|
170
|
-
path: healtCheckPath,
|
|
171
|
-
},
|
|
172
|
-
}, { parent: this, dependsOn: [this.lb] });
|
|
173
|
-
const lbListener = new aws.lb.Listener(`${serviceName}-lb-listener`, {
|
|
174
|
-
loadBalancerArn: this.lb.arn,
|
|
175
|
-
port: 80,
|
|
176
|
-
defaultActions: [
|
|
177
|
-
{
|
|
178
|
-
type: 'forward',
|
|
179
|
-
targetGroupArn: lbTargetGroup.arn,
|
|
180
|
-
},
|
|
181
|
-
],
|
|
182
|
-
}, { parent: this, dependsOn: [this.lb, lbTargetGroup] });
|
|
112
|
+
if (!this.hostedZoneId)
|
|
113
|
+
throw new MissingHostedZoneId(options.type);
|
|
114
|
+
const { serviceName, environment } = options, ecsOptions = __rest(options, ["serviceName", "environment"]);
|
|
183
115
|
const parsedEnv = typeof environment === 'function'
|
|
184
116
|
? environment(this.services)
|
|
185
117
|
: environment;
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
this.services[options.serviceName] = instance;
|
|
118
|
+
const service = new web_server_1.WebServer(serviceName, Object.assign(Object.assign({}, ecsOptions), { cluster: this.cluster, vpc: this.vpc, hostedZoneId: this.hostedZoneId, environment: parsedEnv }), { parent: this });
|
|
119
|
+
this.services[options.serviceName] = service;
|
|
189
120
|
}
|
|
190
121
|
}
|
|
191
122
|
exports.Project = Project;
|