@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 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 | Description |
85
- | :------------- | :---------------------------------------------------------------------: |
86
- | services \* | Service list. |
87
- | environment \* | Environment name. |
88
- | hostedZoneId | Route53 hosted zone ID responsible for managing records for the domain. |
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
+ ![AWS RDS connection schema](/assets/images/ssm-rds.png)
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
+ ![RDS connection](/assets/images/rds-connection.png)
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 { Rds, RdsArgs } from './rds';
6
- import { EcsService, EcsServiceArgs } from './ecs';
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
- export type Service = Rds | Redis | EcsService;
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 & Pick<RdsArgs, 'dbName' | 'username' | 'password' | 'allocatedStorage' | 'maxAllocatedStorage' | 'instanceClass' | 'applyImmediately'>;
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
- healtCheckPath?: pulumi.Input<string>;
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
- dbSubnetGroup: aws.rds.SubnetGroup | null;
35
- dbSecurityGroup: aws.ec2.SecurityGroup | null;
36
- cluster: aws.ecs.Cluster | null;
37
- lbSecurityGroup: aws.ec2.SecurityGroup | null;
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 rds_1 = require("./rds");
20
- const ecs_1 = require("./ecs");
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
- if (!this.dbSecurityGroup || !this.dbSubnetGroup)
140
- return;
141
- const { serviceName, type } = options, rdsOptions = __rest(options, ["serviceName", "type"]);
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 instance = new redis_1.Redis(serviceName, redisOptions, {
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] = instance;
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 || !this.ecsServiceSecurityGroup || !this.lb) {
110
+ if (!this.cluster)
157
111
  return;
158
- }
159
- const { serviceName, environment, healtCheckPath = '/healtcheck' } = options, ecsOptions = __rest(options, ["serviceName", "environment", "healtCheckPath"]);
160
- const lbTargetGroup = new aws.lb.TargetGroup(`${serviceName}-tg`, {
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 instance = new ecs_1.EcsService(serviceName, Object.assign(Object.assign({}, ecsOptions), { cluster: this.cluster, subnets: this.vpc.publicSubnetIds, securityGroupIds: [this.ecsServiceSecurityGroup.id], lb: this.lb, lbTargetGroup,
187
- lbListener, environment: parsedEnv }), { parent: this });
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;