@studion/infra-code-blocks 0.0.11 → 0.1.1

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
@@ -92,7 +92,7 @@ type DatabaseService = {
92
92
  serviceName: string;
93
93
  dbName: pulumi.Input<string>;
94
94
  username: pulumi.Input<string>;
95
- password?: pulumi.Input<string>;
95
+ password: pulumi.Input<string>;
96
96
  applyImmediately?: pulumi.Input<boolean>;
97
97
  skipFinalSnapshot?: pulumi.Input<boolean>;
98
98
  allocatedStorage?: pulumi.Input<number>;
@@ -237,7 +237,7 @@ AWS RDS Postgres instance.
237
237
  Features:
238
238
 
239
239
  - enabled encryption with a symmetric encryption key
240
- - deployed inside a private subnet
240
+ - deployed inside an isolated subnet
241
241
  - backup enabled with retention period set to 14 days
242
242
 
243
243
  <br>
@@ -256,8 +256,8 @@ new Database(name: string, args: DatabaseArgs, opts?: pulumi.CustomResourceOptio
256
256
  type DatabaseArgs = {
257
257
  dbName: pulumi.Input<string>;
258
258
  username: pulumi.Input<string>;
259
+ password: pulumi.Input<string>;
259
260
  vpc: awsx.ec2.Vpc;
260
- password?: pulumi.Input<string>;
261
261
  applyImmediately?: pulumi.Input<boolean>;
262
262
  skipFinalSnapshot?: pulumi.Input<boolean>;
263
263
  allocatedStorage?: pulumi.Input<number>;
@@ -269,9 +269,8 @@ type DatabaseArgs = {
269
269
  };
270
270
  ```
271
271
 
272
- If a password is not specified, it will be autogenerated and stored as a secret
273
- inside AWS Secret Manager. The secret will be available on the `Database` resource
274
- as `passwordSecret`.
272
+ The database password is stored as a secret inside AWS Secret Manager.
273
+ The secret will be available on the `Database` resource as `passwordSecret`.
275
274
 
276
275
  ### Redis
277
276
 
@@ -429,16 +428,16 @@ Where `CLUSTER_NAME` is the name of the ECS cluster and `TASK_FAMILY_NAME` is th
429
428
 
430
429
  ## SSM Connect
431
430
 
432
- The [Database](#database) component deploys a database instance inside a private subnet,
431
+ The [Database](#database) component deploys a database instance inside a isolated subnet,
433
432
  and it's not publicly accessible from outside of VPC.
434
433
  <br>
435
434
  In order to connect to the database we need to deploy the ec2 instance which will be used
436
435
  to forward traffic to the database instance.
437
436
  <br>
438
- Because of security reasons, the ec2 instance is also deployed inside a private subnet
437
+ Because of security reasons, the ec2 instance is deployed inside a private subnet
439
438
  which means we can't directly connect to it. For that purpose, we use AWS System Manager
440
439
  which enables us to connect to the ec2 instance even though it's inside a private subnet.
441
- The benefit of using AWS SSM is that we don't need a ssh key pair.
440
+ Another benefit of using AWS SSM is that we don't need a ssh key pair.
442
441
 
443
442
  ![AWS RDS connection schema](/assets/images/ssm-rds.png)
444
443
 
@@ -501,3 +500,4 @@ const project = new studion.Project('demo-project', {
501
500
 
502
501
  - [ ] Add worker service for executing tasks
503
502
  - [ ] Add MongoDB service
503
+ - [ ] Enable RDS password rotation
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AcmCertificate = void 0;
4
4
  const pulumi = require("@pulumi/pulumi");
5
5
  const aws = require("@pulumi/aws");
6
+ const constants_1 = require("../constants");
6
7
  class AcmCertificate extends pulumi.ComponentResource {
7
8
  constructor(name, args, opts = {}) {
8
9
  super('studion:acm:Certificate', name, {}, opts);
9
- this.certificate = new aws.acm.Certificate(`${args.domain}-certificate`, { domainName: args.domain, validationMethod: 'DNS' }, { parent: this });
10
+ this.certificate = new aws.acm.Certificate(`${args.domain}-certificate`, { domainName: args.domain, validationMethod: 'DNS', tags: constants_1.commonTags }, { parent: this });
10
11
  const certificateValidationDomain = new aws.route53.Record(`${args.domain}-cert-validation-domain`, {
11
12
  name: this.certificate.domainValidationOptions[0].resourceRecordName,
12
13
  type: this.certificate.domainValidationOptions[0].resourceRecordType,
@@ -11,14 +11,14 @@ export type DatabaseArgs = {
11
11
  */
12
12
  username: pulumi.Input<string>;
13
13
  /**
14
- * The awsx.ec2.Vpc resource.
14
+ * Password for the master DB user.
15
+ * The value will be stored as a secret in AWS Secret Manager.
15
16
  */
16
- vpc: awsx.ec2.Vpc;
17
+ password: pulumi.Input<string>;
17
18
  /**
18
- * Password for the master DB user. If not specified, it will be autogenerated.
19
- * The value will be stored as a secret in AWS Secret Manager.
19
+ * The awsx.ec2.Vpc resource.
20
20
  */
21
- password?: pulumi.Input<string>;
21
+ vpc: awsx.ec2.Vpc;
22
22
  /**
23
23
  * Specifies whether any database modifications are applied immediately, or during the next maintenance window. Default is false.
24
24
  */
@@ -47,10 +47,16 @@ export type DatabaseArgs = {
47
47
  }>;
48
48
  };
49
49
  export declare class Database extends pulumi.ComponentResource {
50
+ name: string;
50
51
  instance: aws.rds.Instance;
51
52
  kms: aws.kms.Key;
52
53
  dbSubnetGroup: aws.rds.SubnetGroup;
53
54
  dbSecurityGroup: aws.ec2.SecurityGroup;
54
55
  passwordSecret: aws.secretsmanager.Secret;
55
56
  constructor(name: string, args: DatabaseArgs, opts?: pulumi.ComponentResourceOptions);
57
+ private createSubnetGroup;
58
+ private createSecurityGroup;
59
+ private createEncryptionKey;
60
+ private createPasswordSecret;
61
+ private createDatabaseInstance;
56
62
  }
@@ -3,54 +3,77 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Database = void 0;
4
4
  const aws = require("@pulumi/aws");
5
5
  const pulumi = require("@pulumi/pulumi");
6
+ const constants_1 = require("../constants");
6
7
  const defaults = {
7
8
  applyImmediately: false,
8
9
  skipFinalSnapshot: false,
9
10
  allocatedStorage: 20,
10
11
  maxAllocatedStorage: 100,
11
- instanceClass: 'db.t3.micro',
12
+ instanceClass: 'db.t4g.micro',
12
13
  };
13
14
  class Database extends pulumi.ComponentResource {
14
15
  constructor(name, args, opts = {}) {
15
16
  super('studion:Database', name, {}, opts);
16
- const project = pulumi.getProject();
17
- const stack = pulumi.getStack();
18
- const argsWithDefaults = Object.assign({}, defaults, args);
19
- this.dbSubnetGroup = new aws.rds.SubnetGroup(`${name}-subnet-group`, {
20
- subnetIds: argsWithDefaults.vpc.privateSubnetIds,
17
+ this.name = name;
18
+ const { vpc, password } = args;
19
+ this.dbSubnetGroup = this.createSubnetGroup({ vpc });
20
+ this.dbSecurityGroup = this.createSecurityGroup({ vpc });
21
+ this.kms = this.createEncryptionKey();
22
+ this.passwordSecret = this.createPasswordSecret({ password });
23
+ this.instance = this.createDatabaseInstance(args);
24
+ this.registerOutputs();
25
+ }
26
+ createSubnetGroup({ vpc }) {
27
+ const dbSubnetGroup = new aws.rds.SubnetGroup(`${this.name}-subnet-group`, {
28
+ subnetIds: vpc.isolatedSubnetIds,
29
+ tags: constants_1.commonTags,
21
30
  }, { parent: this });
22
- this.dbSecurityGroup = new aws.ec2.SecurityGroup(`${name}-security-group`, {
23
- vpcId: argsWithDefaults.vpc.vpcId,
31
+ return dbSubnetGroup;
32
+ }
33
+ createSecurityGroup({ vpc }) {
34
+ const dbSecurityGroup = new aws.ec2.SecurityGroup(`${this.name}-security-group`, {
35
+ vpcId: vpc.vpcId,
24
36
  ingress: [
25
37
  {
26
38
  protocol: 'tcp',
27
39
  fromPort: 5432,
28
40
  toPort: 5432,
29
- cidrBlocks: [argsWithDefaults.vpc.vpc.cidrBlock],
41
+ cidrBlocks: [vpc.vpc.cidrBlock],
30
42
  },
31
43
  ],
44
+ tags: constants_1.commonTags,
32
45
  }, { parent: this });
33
- this.kms = new aws.kms.Key(`${name}-rds-key`, {
34
- description: `${name} RDS encryption key`,
46
+ return dbSecurityGroup;
47
+ }
48
+ createEncryptionKey() {
49
+ const kms = new aws.kms.Key(`${this.name}-rds-key`, {
50
+ description: `${this.name} RDS encryption key`,
35
51
  customerMasterKeySpec: 'SYMMETRIC_DEFAULT',
36
52
  isEnabled: true,
37
53
  keyUsage: 'ENCRYPT_DECRYPT',
38
54
  multiRegion: false,
39
55
  enableKeyRotation: true,
56
+ tags: constants_1.commonTags,
40
57
  }, { parent: this });
41
- const password = argsWithDefaults.password ||
42
- aws.secretsmanager
43
- .getRandomPasswordOutput()
44
- .apply(res => res.randomPassword);
45
- this.passwordSecret = new aws.secretsmanager.Secret(`${name}-password-secret`, {
46
- name: `${stack}/${project}/DatabasePassword`,
58
+ return kms;
59
+ }
60
+ createPasswordSecret({ password }) {
61
+ const project = pulumi.getProject();
62
+ const stack = pulumi.getStack();
63
+ const passwordSecret = new aws.secretsmanager.Secret(`${this.name}-password-secret`, {
64
+ namePrefix: `${stack}/${project}/DatabasePassword-`,
65
+ tags: constants_1.commonTags,
47
66
  }, { parent: this });
48
- const passwordSecretValue = new aws.secretsmanager.SecretVersion(`${name}-password-secret-value`, {
49
- secretId: this.passwordSecret.id,
67
+ const passwordSecretValue = new aws.secretsmanager.SecretVersion(`${this.name}-password-secret-value`, {
68
+ secretId: passwordSecret.id,
50
69
  secretString: password,
51
- }, { parent: this, dependsOn: [this.passwordSecret] });
52
- this.instance = new aws.rds.Instance(`${name}-rds`, {
53
- identifier: name,
70
+ }, { parent: this, dependsOn: [passwordSecret] });
71
+ return passwordSecret;
72
+ }
73
+ createDatabaseInstance(args) {
74
+ const argsWithDefaults = Object.assign({}, defaults, args);
75
+ const instance = new aws.rds.Instance(`${this.name}-rds`, {
76
+ identifierPrefix: `${this.name}-`,
54
77
  engine: 'postgres',
55
78
  engineVersion: '14.9',
56
79
  allocatedStorage: argsWithDefaults.allocatedStorage,
@@ -58,7 +81,7 @@ class Database extends pulumi.ComponentResource {
58
81
  instanceClass: argsWithDefaults.instanceClass,
59
82
  dbName: argsWithDefaults.dbName,
60
83
  username: argsWithDefaults.username,
61
- password,
84
+ password: argsWithDefaults.password,
62
85
  dbSubnetGroupName: this.dbSubnetGroup.name,
63
86
  vpcSecurityGroupIds: [this.dbSecurityGroup.id],
64
87
  storageEncrypted: true,
@@ -68,12 +91,12 @@ class Database extends pulumi.ComponentResource {
68
91
  applyImmediately: argsWithDefaults.applyImmediately,
69
92
  autoMinorVersionUpgrade: true,
70
93
  maintenanceWindow: 'Mon:07:00-Mon:07:30',
71
- finalSnapshotIdentifier: `${name}-final-snapshot`,
94
+ finalSnapshotIdentifier: `${this.name}-final-snapshot`,
72
95
  backupWindow: '06:00-06:30',
73
96
  backupRetentionPeriod: 14,
74
- tags: argsWithDefaults.tags,
97
+ tags: Object.assign(Object.assign({}, constants_1.commonTags), argsWithDefaults.tags),
75
98
  }, { parent: this });
76
- this.registerOutputs();
99
+ return instance;
77
100
  }
78
101
  }
79
102
  exports.Database = Database;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Ec2SSMConnect = void 0;
4
4
  const pulumi = require("@pulumi/pulumi");
5
5
  const aws = require("@pulumi/aws");
6
+ const constants_1 = require("../constants");
6
7
  const config = new pulumi.Config('aws');
7
8
  const awsRegion = config.require('region');
8
9
  class Ec2SSMConnect extends pulumi.ComponentResource {
@@ -15,25 +16,20 @@ class Ec2SSMConnect extends pulumi.ComponentResource {
15
16
  protocol: 'tcp',
16
17
  fromPort: 22,
17
18
  toPort: 22,
18
- cidrBlocks: ['0.0.0.0/0'],
19
- },
20
- {
21
- protocol: 'tcp',
22
- fromPort: 80,
23
- toPort: 80,
24
- cidrBlocks: ['0.0.0.0/0'],
19
+ cidrBlocks: [args.vpc.vpc.cidrBlock],
25
20
  },
26
21
  {
27
22
  protocol: 'tcp',
28
23
  fromPort: 443,
29
24
  toPort: 443,
30
- cidrBlocks: ['0.0.0.0/0'],
25
+ cidrBlocks: [args.vpc.vpc.cidrBlock],
31
26
  },
32
27
  ],
33
28
  egress: [
34
29
  { protocol: '-1', fromPort: 0, toPort: 0, cidrBlocks: ['0.0.0.0/0'] },
35
30
  ],
36
31
  vpcId: args.vpc.vpcId,
32
+ tags: constants_1.commonTags,
37
33
  }, { parent: this });
38
34
  const role = new aws.iam.Role(`${name}-ec2-role`, {
39
35
  assumeRolePolicy: {
@@ -48,6 +44,7 @@ class Ec2SSMConnect extends pulumi.ComponentResource {
48
44
  },
49
45
  ],
50
46
  },
47
+ tags: constants_1.commonTags,
51
48
  }, { parent: this });
52
49
  const ssmPolicyAttachment = new aws.iam.RolePolicyAttachment(`${name}-ssm-policy-attachment`, {
53
50
  role: role.name,
@@ -55,15 +52,16 @@ class Ec2SSMConnect extends pulumi.ComponentResource {
55
52
  }, { parent: this });
56
53
  const ssmProfile = new aws.iam.InstanceProfile(`${name}-ssm-profile`, {
57
54
  role: role.name,
55
+ tags: constants_1.commonTags,
58
56
  }, { parent: this, dependsOn: [ssmPolicyAttachment] });
59
57
  this.ec2 = new aws.ec2.Instance(`${name}-ec2`, {
60
- ami: 'ami-067d1e60475437da2',
58
+ ami: constants_1.Ec2AMI.AmazonLinux2023.ARM,
61
59
  associatePublicIpAddress: false,
62
- instanceType: 't2.micro',
60
+ instanceType: 't4g.nano',
63
61
  iamInstanceProfile: ssmProfile.name,
64
62
  subnetId,
65
63
  vpcSecurityGroupIds: [this.ec2SecurityGroup.id],
66
- tags: Object.assign({ Name: `${name}-ec2` }, args.tags),
64
+ tags: Object.assign(Object.assign(Object.assign({}, constants_1.commonTags), { Name: `${name}-ec2` }), args.tags),
67
65
  }, { parent: this });
68
66
  this.ssmVpcEndpoint = new aws.ec2.VpcEndpoint(`${name}-ssm-vpc-endpoint`, {
69
67
  vpcId: args.vpc.vpcId,
@@ -73,6 +71,7 @@ class Ec2SSMConnect extends pulumi.ComponentResource {
73
71
  subnetIds: [subnetId],
74
72
  securityGroupIds: [this.ec2SecurityGroup.id],
75
73
  privateDnsEnabled: true,
74
+ tags: constants_1.commonTags,
76
75
  }, { parent: this, dependsOn: [this.ec2] });
77
76
  this.ec2MessagesVpcEndpoint = new aws.ec2.VpcEndpoint(`${name}-ec2messages-vpc-endpoint`, {
78
77
  vpcId: args.vpc.vpcId,
@@ -82,6 +81,7 @@ class Ec2SSMConnect extends pulumi.ComponentResource {
82
81
  subnetIds: [subnetId],
83
82
  securityGroupIds: [this.ec2SecurityGroup.id],
84
83
  privateDnsEnabled: true,
84
+ tags: constants_1.commonTags,
85
85
  }, { parent: this, dependsOn: [this.ec2] });
86
86
  this.ssmMessagesVpcEndpoint = new aws.ec2.VpcEndpoint(`${name}-ssmmessages-vpc-endpoint`, {
87
87
  vpcId: args.vpc.vpcId,
@@ -91,6 +91,7 @@ class Ec2SSMConnect extends pulumi.ComponentResource {
91
91
  subnetIds: [subnetId],
92
92
  securityGroupIds: [this.ec2SecurityGroup.id],
93
93
  privateDnsEnabled: true,
94
+ tags: constants_1.commonTags,
94
95
  }, { parent: this, dependsOn: [this.ec2] });
95
96
  this.registerOutputs();
96
97
  }
@@ -21,6 +21,7 @@ const web_server_1 = require("./web-server");
21
21
  const redis_1 = require("./redis");
22
22
  const static_site_1 = require("./static-site");
23
23
  const ec2_ssm_connect_1 = require("./ec2-ssm-connect");
24
+ const constants_1 = require("../constants");
24
25
  class MissingHostedZoneId extends Error {
25
26
  constructor(serviceType) {
26
27
  super(`Project::hostedZoneId argument must be provided
@@ -50,6 +51,12 @@ class Project extends pulumi.ComponentResource {
50
51
  numberOfAvailabilityZones: 2,
51
52
  enableDnsHostnames: true,
52
53
  enableDnsSupport: true,
54
+ subnetSpecs: [
55
+ { type: awsx.ec2.SubnetType.Public, cidrMask: 24 },
56
+ { type: awsx.ec2.SubnetType.Private, cidrMask: 24 },
57
+ { type: awsx.ec2.SubnetType.Isolated, cidrMask: 24 },
58
+ ],
59
+ tags: constants_1.commonTags,
53
60
  }, { parent: this });
54
61
  return vpc;
55
62
  }
@@ -81,6 +88,7 @@ class Project extends pulumi.ComponentResource {
81
88
  createWebServerPrerequisites() {
82
89
  this.cluster = new aws.ecs.Cluster(`${this.name}-cluster`, {
83
90
  name: this.name,
91
+ tags: constants_1.commonTags,
84
92
  }, { parent: this });
85
93
  }
86
94
  createDatabaseService(options) {
@@ -4,6 +4,7 @@ exports.Redis = void 0;
4
4
  const pulumi = require("@pulumi/pulumi");
5
5
  const upstash = require("@upstash/pulumi");
6
6
  const aws = require("@pulumi/aws");
7
+ const constants_1 = require("../constants");
7
8
  const defaults = {
8
9
  region: 'us-east-1',
9
10
  };
@@ -21,7 +22,8 @@ class Redis extends pulumi.ComponentResource {
21
22
  tls: true,
22
23
  }, { provider: opts.provider, parent: this });
23
24
  this.passwordSecret = new aws.secretsmanager.Secret(`${name}-password-secret`, {
24
- name: `${stack}/${project}/RedisPassword`,
25
+ namePrefix: `${stack}/${project}/RedisPassword-`,
26
+ tags: constants_1.commonTags,
25
27
  }, { parent: this, dependsOn: [this.instance] });
26
28
  const passwordSecretValue = new aws.secretsmanager.SecretVersion(`${name}-password-secret-value`, {
27
29
  secretId: this.passwordSecret.id,
@@ -19,8 +19,13 @@ export type StaticSiteArgs = {
19
19
  }>;
20
20
  };
21
21
  export declare class StaticSite extends pulumi.ComponentResource {
22
+ name: string;
22
23
  certificate: AcmCertificate;
23
24
  bucket: aws.s3.Bucket;
24
25
  cloudfront: aws.cloudfront.Distribution;
25
26
  constructor(name: string, args: StaticSiteArgs, opts?: pulumi.ComponentResourceOptions);
27
+ private createTlsCertificate;
28
+ private createPublicBucket;
29
+ private createCloudfrontDistribution;
30
+ private createDnsRecord;
26
31
  }
@@ -4,28 +4,42 @@ exports.StaticSite = void 0;
4
4
  const aws = require("@pulumi/aws");
5
5
  const pulumi = require("@pulumi/pulumi");
6
6
  const acm_certificate_1 = require("./acm-certificate");
7
+ const constants_1 = require("../constants");
7
8
  class StaticSite extends pulumi.ComponentResource {
8
9
  constructor(name, args, opts = {}) {
9
10
  super('studion:StaticSite', name, {}, opts);
10
- const certificate = new acm_certificate_1.AcmCertificate(`${args.domain}-acm-certificate`, {
11
- domain: args.domain,
12
- hostedZoneId: args.hostedZoneId,
11
+ this.name = name;
12
+ const { domain, hostedZoneId, tags } = args;
13
+ this.certificate = this.createTlsCertificate({ domain, hostedZoneId });
14
+ this.bucket = this.createPublicBucket({ tags });
15
+ this.cloudfront = this.createCloudfrontDistribution({ domain, tags });
16
+ this.createDnsRecord({ domain, hostedZoneId });
17
+ this.registerOutputs();
18
+ }
19
+ createTlsCertificate({ domain, hostedZoneId, }) {
20
+ const certificate = new acm_certificate_1.AcmCertificate(`${domain}-acm-certificate`, {
21
+ domain,
22
+ hostedZoneId,
13
23
  }, { parent: this });
14
- const bucket = new aws.s3.Bucket(`${name}-bucket`, {
24
+ return certificate;
25
+ }
26
+ createPublicBucket({ tags }) {
27
+ const bucket = new aws.s3.Bucket(`${this.name}-bucket`, {
28
+ bucketPrefix: `${this.name}-`,
15
29
  website: {
16
30
  indexDocument: 'index.html',
17
31
  errorDocument: 'index.html',
18
32
  },
19
- tags: args.tags,
33
+ tags: Object.assign(Object.assign({}, constants_1.commonTags), tags),
20
34
  }, { parent: this });
21
- const bucketPublicAccessBlock = new aws.s3.BucketPublicAccessBlock(`${name}-bucket-access-block`, {
35
+ const bucketPublicAccessBlock = new aws.s3.BucketPublicAccessBlock(`${this.name}-bucket-access-block`, {
22
36
  bucket: bucket.id,
23
37
  blockPublicAcls: false,
24
38
  blockPublicPolicy: false,
25
39
  ignorePublicAcls: false,
26
40
  restrictPublicBuckets: false,
27
41
  }, { parent: this });
28
- const siteBucketPolicy = new aws.s3.BucketPolicy(`${name}-bucket-policy`, {
42
+ const siteBucketPolicy = new aws.s3.BucketPolicy(`${this.name}-bucket-policy`, {
29
43
  bucket: bucket.bucket,
30
44
  policy: bucket.bucket.apply(publicReadPolicy),
31
45
  }, { parent: this, dependsOn: [bucketPublicAccessBlock] });
@@ -42,22 +56,25 @@ class StaticSite extends pulumi.ComponentResource {
42
56
  ],
43
57
  };
44
58
  }
45
- const cloudfront = new aws.cloudfront.Distribution(`${name}-cloudfront`, {
59
+ return bucket;
60
+ }
61
+ createCloudfrontDistribution({ domain, tags, }) {
62
+ const cloudfront = new aws.cloudfront.Distribution(`${this.name}-cloudfront`, {
46
63
  enabled: true,
47
64
  defaultRootObject: 'index.html',
48
- aliases: [args.domain],
65
+ aliases: [domain],
49
66
  isIpv6Enabled: true,
50
67
  waitForDeployment: true,
51
68
  httpVersion: 'http2and3',
52
69
  viewerCertificate: {
53
- acmCertificateArn: certificate.certificate.arn,
70
+ acmCertificateArn: this.certificate.certificate.arn,
54
71
  sslSupportMethod: 'sni-only',
55
72
  minimumProtocolVersion: 'TLSv1.2_2021',
56
73
  },
57
74
  origins: [
58
75
  {
59
- originId: bucket.arn,
60
- domainName: bucket.websiteEndpoint,
76
+ originId: this.bucket.arn,
77
+ domainName: this.bucket.websiteEndpoint,
61
78
  connectionAttempts: 3,
62
79
  connectionTimeout: 10,
63
80
  customOriginConfig: {
@@ -69,7 +86,7 @@ class StaticSite extends pulumi.ComponentResource {
69
86
  },
70
87
  ],
71
88
  defaultCacheBehavior: {
72
- targetOriginId: bucket.arn,
89
+ targetOriginId: this.bucket.arn,
73
90
  viewerProtocolPolicy: 'redirect-to-https',
74
91
  allowedMethods: ['GET', 'HEAD', 'OPTIONS'],
75
92
  cachedMethods: ['GET', 'HEAD', 'OPTIONS'],
@@ -86,24 +103,24 @@ class StaticSite extends pulumi.ComponentResource {
86
103
  restrictions: {
87
104
  geoRestriction: { restrictionType: 'none' },
88
105
  },
89
- tags: args.tags,
106
+ tags: Object.assign(Object.assign({}, constants_1.commonTags), tags),
90
107
  }, { parent: this });
91
- const cdnAliasRecord = new aws.route53.Record(`${name}-cdn-route53-record`, {
108
+ return cloudfront;
109
+ }
110
+ createDnsRecord({ domain, hostedZoneId, }) {
111
+ const cdnAliasRecord = new aws.route53.Record(`${this.name}-cdn-route53-record`, {
92
112
  type: 'A',
93
- name: args.domain,
94
- zoneId: args.hostedZoneId,
113
+ name: domain,
114
+ zoneId: hostedZoneId,
95
115
  aliases: [
96
116
  {
97
- name: cloudfront.domainName,
98
- zoneId: cloudfront.hostedZoneId,
117
+ name: this.cloudfront.domainName,
118
+ zoneId: this.cloudfront.hostedZoneId,
99
119
  evaluateTargetHealth: true,
100
120
  },
101
121
  ],
102
122
  }, { parent: this });
103
- this.certificate = certificate;
104
- this.bucket = bucket;
105
- this.cloudfront = cloudfront;
106
- this.registerOutputs();
123
+ return cdnAliasRecord;
107
124
  }
108
125
  }
109
126
  exports.StaticSite = StaticSite;
@@ -1,7 +1,7 @@
1
1
  import * as pulumi from '@pulumi/pulumi';
2
2
  import * as aws from '@pulumi/aws';
3
3
  import * as awsx from '@pulumi/awsx';
4
- import { Size } from './types';
4
+ import { Size } from '../types/size';
5
5
  import { AcmCertificate } from './acm-certificate';
6
6
  export type RoleInlinePolicy = {
7
7
  /**
@@ -85,6 +85,7 @@ export type WebServerArgs = {
85
85
  }>;
86
86
  };
87
87
  export declare class WebServer extends pulumi.ComponentResource {
88
+ name: string;
88
89
  certificate: AcmCertificate;
89
90
  logGroup: aws.cloudwatch.LogGroup;
90
91
  lbSecurityGroup: aws.ec2.SecurityGroup;
@@ -92,8 +93,14 @@ export declare class WebServer extends pulumi.ComponentResource {
92
93
  lbTargetGroup: aws.lb.TargetGroup;
93
94
  lbHttpListener: aws.lb.Listener;
94
95
  lbTlsListener: aws.lb.Listener;
95
- serviceSecurityGroup: aws.ec2.SecurityGroup;
96
96
  taskDefinition: aws.ecs.TaskDefinition;
97
97
  service: aws.ecs.Service;
98
98
  constructor(name: string, args: WebServerArgs, opts?: pulumi.ComponentResourceOptions);
99
+ private createTlsCertificate;
100
+ private createLogGroup;
101
+ private createLoadBalancer;
102
+ private createTaskDefinition;
103
+ private createEcsService;
104
+ private createDnsRecord;
105
+ private enableAutoscaling;
99
106
  }
@@ -34,17 +34,40 @@ const defaults = {
34
34
  class WebServer extends pulumi.ComponentResource {
35
35
  constructor(name, args, opts = {}) {
36
36
  super('studion:WebServer', name, {}, opts);
37
- const argsWithDefaults = Object.assign({}, defaults, args);
38
- this.certificate = new acm_certificate_1.AcmCertificate(`${argsWithDefaults.domain}-acm-certificate`, {
39
- domain: argsWithDefaults.domain,
40
- hostedZoneId: argsWithDefaults.hostedZoneId,
37
+ this.name = name;
38
+ const { domain, hostedZoneId, vpc, port, healtCheckPath } = args;
39
+ this.certificate = this.createTlsCertificate({ domain, hostedZoneId });
40
+ this.logGroup = this.createLogGroup();
41
+ const { lb, lbTargetGroup, lbHttpListener, lbTlsListener, lbSecurityGroup, } = this.createLoadBalancer({ vpc, port, healtCheckPath });
42
+ this.lb = lb;
43
+ this.lbTargetGroup = lbTargetGroup;
44
+ this.lbHttpListener = lbHttpListener;
45
+ this.lbTlsListener = lbTlsListener;
46
+ this.lbSecurityGroup = lbSecurityGroup;
47
+ this.taskDefinition = this.createTaskDefinition(args);
48
+ this.service = this.createEcsService(args);
49
+ this.createDnsRecord({ domain, hostedZoneId });
50
+ this.enableAutoscaling(args);
51
+ this.registerOutputs();
52
+ }
53
+ createTlsCertificate({ domain, hostedZoneId, }) {
54
+ const certificate = new acm_certificate_1.AcmCertificate(`${domain}-acm-certificate`, {
55
+ domain,
56
+ hostedZoneId,
41
57
  }, { parent: this });
42
- this.logGroup = new aws.cloudwatch.LogGroup(`${name}-log-group`, {
58
+ return certificate;
59
+ }
60
+ createLogGroup() {
61
+ const logGroup = new aws.cloudwatch.LogGroup(`${this.name}-log-group`, {
43
62
  retentionInDays: 14,
44
- name: `/ecs/${name}`,
63
+ namePrefix: `/ecs/${this.name}-`,
64
+ tags: constants_1.commonTags,
45
65
  }, { parent: this });
46
- this.lbSecurityGroup = new aws.ec2.SecurityGroup(`${name}-lb-security-group`, {
47
- vpcId: argsWithDefaults.vpc.vpcId,
66
+ return logGroup;
67
+ }
68
+ createLoadBalancer({ vpc, port, healtCheckPath, }) {
69
+ const lbSecurityGroup = new aws.ec2.SecurityGroup(`${this.name}-lb-security-group`, {
70
+ vpcId: vpc.vpcId,
48
71
  ingress: [
49
72
  {
50
73
  protocol: 'tcp',
@@ -67,30 +90,33 @@ class WebServer extends pulumi.ComponentResource {
67
90
  cidrBlocks: ['0.0.0.0/0'],
68
91
  },
69
92
  ],
93
+ tags: constants_1.commonTags,
70
94
  }, { parent: this });
71
- this.lb = new aws.lb.LoadBalancer(`${name}-lb`, {
72
- name: `${name}-lb`,
95
+ const lb = new aws.lb.LoadBalancer(`${this.name}-lb`, {
96
+ namePrefix: `${this.name}-lb-`,
73
97
  loadBalancerType: 'application',
74
- subnets: argsWithDefaults.vpc.publicSubnetIds,
75
- securityGroups: [this.lbSecurityGroup.id],
98
+ subnets: vpc.publicSubnetIds,
99
+ securityGroups: [lbSecurityGroup.id],
76
100
  internal: false,
77
101
  ipAddressType: 'ipv4',
102
+ tags: constants_1.commonTags,
78
103
  }, { parent: this });
79
- this.lbTargetGroup = new aws.lb.TargetGroup(`${name}-lb-tg`, {
80
- name: `${name}-lb-tg`,
81
- port: argsWithDefaults.port,
104
+ const lbTargetGroup = new aws.lb.TargetGroup(`${this.name}-lb-tg`, {
105
+ namePrefix: `${this.name}-lb-tg-`,
106
+ port,
82
107
  protocol: 'HTTP',
83
108
  targetType: 'ip',
84
- vpcId: argsWithDefaults.vpc.vpcId,
109
+ vpcId: vpc.vpcId,
85
110
  healthCheck: {
86
111
  healthyThreshold: 3,
87
112
  unhealthyThreshold: 2,
88
113
  interval: 60,
89
114
  timeout: 5,
90
- path: argsWithDefaults.healtCheckPath,
115
+ path: healtCheckPath || defaults.healtCheckPath,
91
116
  },
117
+ tags: constants_1.commonTags,
92
118
  }, { parent: this, dependsOn: [this.lb] });
93
- this.lbHttpListener = new aws.lb.Listener(`${name}-lb-listener-80`, {
119
+ const lbHttpListener = new aws.lb.Listener(`${this.name}-lb-listener-80`, {
94
120
  loadBalancerArn: this.lb.arn,
95
121
  port: 80,
96
122
  defaultActions: [
@@ -103,8 +129,9 @@ class WebServer extends pulumi.ComponentResource {
103
129
  },
104
130
  },
105
131
  ],
132
+ tags: constants_1.commonTags,
106
133
  }, { parent: this });
107
- this.lbTlsListener = new aws.lb.Listener(`${name}-lb-listener-443`, {
134
+ const lbTlsListener = new aws.lb.Listener(`${this.name}-lb-listener-443`, {
108
135
  loadBalancerArn: this.lb.arn,
109
136
  port: 443,
110
137
  protocol: 'HTTPS',
@@ -116,21 +143,20 @@ class WebServer extends pulumi.ComponentResource {
116
143
  targetGroupArn: this.lbTargetGroup.arn,
117
144
  },
118
145
  ],
146
+ tags: constants_1.commonTags,
119
147
  }, { parent: this });
120
- const albAliasRecord = new aws.route53.Record(`${name}-route53-record`, {
121
- type: 'A',
122
- name: argsWithDefaults.domain,
123
- zoneId: argsWithDefaults.hostedZoneId,
124
- aliases: [
125
- {
126
- name: this.lb.dnsName,
127
- zoneId: this.lb.zoneId,
128
- evaluateTargetHealth: true,
129
- },
130
- ],
131
- }, { parent: this });
148
+ return {
149
+ lb,
150
+ lbTargetGroup,
151
+ lbHttpListener,
152
+ lbTlsListener,
153
+ lbSecurityGroup,
154
+ };
155
+ }
156
+ createTaskDefinition(args) {
157
+ const argsWithDefaults = Object.assign({}, defaults, args);
132
158
  const secretManagerSecretsInlinePolicy = {
133
- name: `${name}-secret-manager-access`,
159
+ name: `${this.name}-secret-manager-access`,
134
160
  policy: JSON.stringify({
135
161
  Version: '2012-10-17',
136
162
  Statement: [
@@ -143,8 +169,8 @@ class WebServer extends pulumi.ComponentResource {
143
169
  ],
144
170
  }),
145
171
  };
146
- const taskExecutionRole = new aws.iam.Role(`${name}-ecs-task-exec-role`, {
147
- name: `${name}-ecs-task-exec-role`,
172
+ const taskExecutionRole = new aws.iam.Role(`${this.name}-ecs-task-exec-role`, {
173
+ namePrefix: `${this.name}-ecs-task-exec-role-`,
148
174
  assumeRolePolicy,
149
175
  managedPolicyArns: [
150
176
  'arn:aws:iam::aws:policy/CloudWatchFullAccess',
@@ -154,9 +180,10 @@ class WebServer extends pulumi.ComponentResource {
154
180
  secretManagerSecretsInlinePolicy,
155
181
  ...argsWithDefaults.taskExecutionRoleInlinePolicies,
156
182
  ],
183
+ tags: constants_1.commonTags,
157
184
  }, { parent: this });
158
185
  const execCmdInlinePolicy = {
159
- name: `${name}-ecs-exec`,
186
+ name: `${this.name}-ecs-exec`,
160
187
  policy: JSON.stringify({
161
188
  Version: '2012-10-17',
162
189
  Statement: [
@@ -174,13 +201,14 @@ class WebServer extends pulumi.ComponentResource {
174
201
  ],
175
202
  }),
176
203
  };
177
- const taskRole = new aws.iam.Role(`${name}-ecs-task-role`, {
178
- name: `${name}-ecs-task-role`,
204
+ const taskRole = new aws.iam.Role(`${this.name}-ecs-task-role`, {
205
+ namePrefix: `${this.name}-ecs-task-role-`,
179
206
  assumeRolePolicy,
180
207
  inlinePolicies: [
181
208
  execCmdInlinePolicy,
182
209
  ...argsWithDefaults.taskRoleInlinePolicies,
183
210
  ],
211
+ tags: constants_1.commonTags,
184
212
  }, { parent: this });
185
213
  const parsedSize = pulumi.all([argsWithDefaults.size]).apply(([size]) => {
186
214
  const mapCapabilities = ({ cpu, memory }) => ({
@@ -195,8 +223,8 @@ class WebServer extends pulumi.ComponentResource {
195
223
  }
196
224
  throw Error('Incorrect EcsService size argument');
197
225
  });
198
- this.taskDefinition = new aws.ecs.TaskDefinition(`${name}-task-definition`, {
199
- family: `${name}-task-definition`,
226
+ const taskDefinition = new aws.ecs.TaskDefinition(`${this.name}-task-definition`, {
227
+ family: `${this.name}-task-definition`,
200
228
  networkMode: 'awsvpc',
201
229
  executionRoleArn: taskExecutionRole.arn,
202
230
  taskRoleArn: taskRole.arn,
@@ -205,7 +233,7 @@ class WebServer extends pulumi.ComponentResource {
205
233
  requiresCompatibilities: ['FARGATE'],
206
234
  containerDefinitions: pulumi
207
235
  .all([
208
- name,
236
+ this.name,
209
237
  argsWithDefaults.image,
210
238
  argsWithDefaults.port,
211
239
  argsWithDefaults.environment,
@@ -239,9 +267,13 @@ class WebServer extends pulumi.ComponentResource {
239
267
  },
240
268
  ]);
241
269
  }),
242
- tags: argsWithDefaults.tags,
270
+ tags: Object.assign(Object.assign({}, constants_1.commonTags), argsWithDefaults.tags),
243
271
  }, { parent: this });
244
- this.serviceSecurityGroup = new aws.ec2.SecurityGroup(`${name}-security-group`, {
272
+ return taskDefinition;
273
+ }
274
+ createEcsService(args) {
275
+ const argsWithDefaults = Object.assign({}, defaults, args);
276
+ const serviceSecurityGroup = new aws.ec2.SecurityGroup(`${this.name}-security-group`, {
245
277
  vpcId: argsWithDefaults.vpc.vpcId,
246
278
  ingress: [
247
279
  {
@@ -259,9 +291,10 @@ class WebServer extends pulumi.ComponentResource {
259
291
  cidrBlocks: ['0.0.0.0/0'],
260
292
  },
261
293
  ],
294
+ tags: constants_1.commonTags,
262
295
  }, { parent: this });
263
- this.service = new aws.ecs.Service(`${name}-service`, {
264
- name,
296
+ const service = new aws.ecs.Service(`${this.name}-service`, {
297
+ name: this.name,
265
298
  cluster: argsWithDefaults.cluster.id,
266
299
  launchType: 'FARGATE',
267
300
  desiredCount: argsWithDefaults.desiredCount,
@@ -269,7 +302,7 @@ class WebServer extends pulumi.ComponentResource {
269
302
  enableExecuteCommand: true,
270
303
  loadBalancers: [
271
304
  {
272
- containerName: name,
305
+ containerName: this.name,
273
306
  containerPort: argsWithDefaults.port,
274
307
  targetGroupArn: this.lbTargetGroup.arn,
275
308
  },
@@ -277,9 +310,9 @@ class WebServer extends pulumi.ComponentResource {
277
310
  networkConfiguration: {
278
311
  assignPublicIp: true,
279
312
  subnets: argsWithDefaults.vpc.publicSubnetIds,
280
- securityGroups: [this.serviceSecurityGroup.id],
313
+ securityGroups: [serviceSecurityGroup.id],
281
314
  },
282
- tags: argsWithDefaults.tags,
315
+ tags: Object.assign(Object.assign({}, constants_1.commonTags), argsWithDefaults.tags),
283
316
  }, {
284
317
  parent: this,
285
318
  dependsOn: [
@@ -289,14 +322,33 @@ class WebServer extends pulumi.ComponentResource {
289
322
  this.lbTlsListener,
290
323
  ],
291
324
  });
292
- const autoscalingTarget = new aws.appautoscaling.Target(`${name}-autoscale-target`, {
325
+ return service;
326
+ }
327
+ createDnsRecord({ domain, hostedZoneId, }) {
328
+ const albAliasRecord = new aws.route53.Record(`${this.name}-route53-record`, {
329
+ type: 'A',
330
+ name: domain,
331
+ zoneId: hostedZoneId,
332
+ aliases: [
333
+ {
334
+ name: this.lb.dnsName,
335
+ zoneId: this.lb.zoneId,
336
+ evaluateTargetHealth: true,
337
+ },
338
+ ],
339
+ }, { parent: this });
340
+ }
341
+ enableAutoscaling(args) {
342
+ const argsWithDefaults = Object.assign({}, defaults, args);
343
+ const autoscalingTarget = new aws.appautoscaling.Target(`${this.name}-autoscale-target`, {
293
344
  minCapacity: argsWithDefaults.minCount,
294
345
  maxCapacity: argsWithDefaults.maxCount,
295
346
  resourceId: pulumi.interpolate `service/${argsWithDefaults.cluster.name}/${this.service.name}`,
296
347
  serviceNamespace: 'ecs',
297
348
  scalableDimension: 'ecs:service:DesiredCount',
349
+ tags: constants_1.commonTags,
298
350
  }, { parent: this });
299
- const memoryAutoscalingPolicy = new aws.appautoscaling.Policy(`${name}-memory-autoscale-policy`, {
351
+ const memoryAutoscalingPolicy = new aws.appautoscaling.Policy(`${this.name}-memory-autoscale-policy`, {
300
352
  policyType: 'TargetTrackingScaling',
301
353
  resourceId: autoscalingTarget.resourceId,
302
354
  scalableDimension: autoscalingTarget.scalableDimension,
@@ -308,7 +360,7 @@ class WebServer extends pulumi.ComponentResource {
308
360
  targetValue: 80,
309
361
  },
310
362
  }, { parent: this });
311
- const cpuAutoscalingPolicy = new aws.appautoscaling.Policy(`${name}-cpu-autoscale-policy`, {
363
+ const cpuAutoscalingPolicy = new aws.appautoscaling.Policy(`${this.name}-cpu-autoscale-policy`, {
312
364
  policyType: 'TargetTrackingScaling',
313
365
  resourceId: autoscalingTarget.resourceId,
314
366
  scalableDimension: autoscalingTarget.scalableDimension,
@@ -320,7 +372,6 @@ class WebServer extends pulumi.ComponentResource {
320
372
  targetValue: 60,
321
373
  },
322
374
  }, { parent: this });
323
- this.registerOutputs();
324
375
  }
325
376
  }
326
377
  exports.WebServer = WebServer;
@@ -16,3 +16,12 @@ export declare const PredefinedSize: {
16
16
  readonly memory: number;
17
17
  };
18
18
  };
19
+ export declare const Ec2AMI: {
20
+ AmazonLinux2023: {
21
+ ARM: string;
22
+ };
23
+ };
24
+ export declare const commonTags: {
25
+ Env: string;
26
+ Project: string;
27
+ };
package/dist/constants.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PredefinedSize = void 0;
3
+ exports.commonTags = exports.Ec2AMI = exports.PredefinedSize = void 0;
4
+ const pulumi = require("@pulumi/pulumi");
4
5
  const CPU_1_VCPU = 1024;
5
6
  const MEMORY_1GB = 1024;
6
7
  exports.PredefinedSize = {
@@ -21,3 +22,12 @@ exports.PredefinedSize = {
21
22
  memory: MEMORY_1GB * 4, // 4 GB memory
22
23
  },
23
24
  };
25
+ exports.Ec2AMI = {
26
+ AmazonLinux2023: {
27
+ ARM: 'ami-0b40baa8c6b882e6c',
28
+ },
29
+ };
30
+ exports.commonTags = {
31
+ Env: pulumi.getStack(),
32
+ Project: pulumi.getProject(),
33
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studion/infra-code-blocks",
3
- "version": "0.0.11",
3
+ "version": "0.1.1",
4
4
  "description": "Studion common infra components",
5
5
  "keywords": [
6
6
  "infrastructure",
File without changes
File without changes