@studion/infra-code-blocks 0.0.8 → 0.0.10

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>;
@@ -131,6 +131,7 @@ export type WebServerService = {
131
131
  environment?:
132
132
  | aws.ecs.KeyValuePair[]
133
133
  | ((services: Services) => aws.ecs.KeyValuePair[]);
134
+ secrets?: aws.ecs.Secret[] | ((services: Services) => aws.ecs.Secret[]);
134
135
  image: pulumi.Input<string>;
135
136
  port: pulumi.Input<number>;
136
137
  domain: pulumi.Input<string>;
@@ -181,6 +182,54 @@ const project = new studion.Project('demo-project', {
181
182
  });
182
183
  ```
183
184
 
185
+ In order to pass sensitive information to the container use `secrets` instead of `environment`. AWS will fetch values from
186
+ Secret Manager based on arn that is provided for the `valueFrom` field.
187
+
188
+ ```ts
189
+ const project = new studion.Project('demo-project', {
190
+ environment: 'DEVELOPMENT',
191
+ services: [
192
+ {
193
+ type: 'WEB_SERVER',
194
+ serviceName: 'api',
195
+ image: imageUri,
196
+ port: 3000,
197
+ domain: 'api.my-domain.com',
198
+ secrets: [
199
+ { name: 'DB_PASSWORD', valueFrom: 'arn-of-the-secret-manager-secret' },
200
+ ],
201
+ },
202
+ ],
203
+ });
204
+ ```
205
+
206
+ ```ts
207
+ const project = new studion.Project('demo-project', {
208
+ environment: 'DEVELOPMENT',
209
+ services: [
210
+ {
211
+ type: 'REDIS',
212
+ serviceName: 'redis',
213
+ dbName: 'test-db',
214
+ },
215
+ {
216
+ type: 'WEB_SERVER',
217
+ serviceName: 'api',
218
+ image: imageUri,
219
+ port: 3000,
220
+ domain: 'api.my-domain.com',
221
+ secrets: (services: Services) => {
222
+ const redisServiceName = 'redis';
223
+ const redis = services[redisServiceName];
224
+ return [
225
+ { name: 'REDIS_PASSWORD', valueFrom: redis.passwordSecret.arn },
226
+ ];
227
+ },
228
+ },
229
+ ],
230
+ });
231
+ ```
232
+
184
233
  ### Database
185
234
 
186
235
  AWS RDS Postgres instance.
@@ -207,8 +256,8 @@ new Database(name: string, args: DatabaseArgs, opts?: pulumi.CustomResourceOptio
207
256
  type DatabaseArgs = {
208
257
  dbName: pulumi.Input<string>;
209
258
  username: pulumi.Input<string>;
210
- password: pulumi.Input<string>;
211
259
  vpc: awsx.ec2.Vpc;
260
+ password?: pulumi.Input<string>;
212
261
  applyImmediately?: pulumi.Input<boolean>;
213
262
  skipFinalSnapshot?: pulumi.Input<boolean>;
214
263
  allocatedStorage?: pulumi.Input<number>;
@@ -220,6 +269,10 @@ type DatabaseArgs = {
220
269
  };
221
270
  ```
222
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`.
275
+
223
276
  ### Redis
224
277
 
225
278
  [Upstash](https://upstash.com) Redis instance.
@@ -261,6 +314,9 @@ interface RedisOptions extends pulumi.ComponentResourceOptions {
261
314
  }
262
315
  ```
263
316
 
317
+ After creating the Redis resource, the `passwordSecret` AWS Secret Manager Secret
318
+ will exist on the resource.
319
+
264
320
  ### Static Site
265
321
 
266
322
  AWS S3 + Cloudfront static site.
@@ -331,6 +387,7 @@ export type WebServerArgs = {
331
387
  maxCount?: pulumi.Input<number>;
332
388
  size?: pulumi.Input<Size>;
333
389
  environment?: aws.ecs.KeyValuePair[];
390
+ secrets?: aws.ecs.Secret[];
334
391
  healtCheckPath?: pulumi.Input<string>;
335
392
  taskExecutionRoleInlinePolicies?: pulumi.Input<
336
393
  pulumi.Input<RoleInlinePolicy>[]
@@ -10,14 +10,15 @@ export type DatabaseArgs = {
10
10
  * Username for the master DB user.
11
11
  */
12
12
  username: pulumi.Input<string>;
13
- /**
14
- * Password for the master DB user.
15
- */
16
- password: pulumi.Input<string>;
17
13
  /**
18
14
  * The awsx.ec2.Vpc resource.
19
15
  */
20
16
  vpc: awsx.ec2.Vpc;
17
+ /**
18
+ * Password for the master DB user. If not specified, it will be autogenerated
19
+ * and stored as a secret in AWS Secret Manager.
20
+ */
21
+ password?: pulumi.Input<string>;
21
22
  /**
22
23
  * Specifies whether any database modifications are applied immediately, or during the next maintenance window. Default is false.
23
24
  */
@@ -50,5 +51,6 @@ export declare class Database extends pulumi.ComponentResource {
50
51
  kms: aws.kms.Key;
51
52
  dbSubnetGroup: aws.rds.SubnetGroup;
52
53
  dbSecurityGroup: aws.ec2.SecurityGroup;
54
+ passwordSecret?: aws.secretsmanager.Secret;
53
55
  constructor(name: string, args: DatabaseArgs, opts?: pulumi.ComponentResourceOptions);
54
56
  }
@@ -13,6 +13,8 @@ const defaults = {
13
13
  class Database extends pulumi.ComponentResource {
14
14
  constructor(name, args, opts = {}) {
15
15
  super('studion:Database', name, {}, opts);
16
+ const project = pulumi.getProject();
17
+ const stack = pulumi.getStack();
16
18
  const argsWithDefaults = Object.assign({}, defaults, args);
17
19
  this.dbSubnetGroup = new aws.rds.SubnetGroup(`${name}-subnet-group`, {
18
20
  subnetIds: argsWithDefaults.vpc.privateSubnetIds,
@@ -36,6 +38,19 @@ class Database extends pulumi.ComponentResource {
36
38
  multiRegion: false,
37
39
  enableKeyRotation: true,
38
40
  }, { parent: this });
41
+ const password = argsWithDefaults.password ||
42
+ aws.secretsmanager
43
+ .getRandomPasswordOutput()
44
+ .apply(res => res.randomPassword);
45
+ if (!argsWithDefaults.password) {
46
+ this.passwordSecret = new aws.secretsmanager.Secret(`${name}-password-secret`, {
47
+ name: `${stack}/${project}/DatabasePassword`,
48
+ }, { parent: this });
49
+ const passwordSecretValue = new aws.secretsmanager.SecretVersion(`${name}-password-secret-value`, {
50
+ secretId: this.passwordSecret.id,
51
+ secretString: password,
52
+ }, { parent: this, dependsOn: [this.passwordSecret] });
53
+ }
39
54
  this.instance = new aws.rds.Instance(`${name}-rds`, {
40
55
  identifier: name,
41
56
  engine: 'postgres',
@@ -45,7 +60,7 @@ class Database extends pulumi.ComponentResource {
45
60
  instanceClass: argsWithDefaults.instanceClass,
46
61
  dbName: argsWithDefaults.dbName,
47
62
  username: argsWithDefaults.username,
48
- password: argsWithDefaults.password,
63
+ password,
49
64
  dbSubnetGroupName: this.dbSubnetGroup.name,
50
65
  vpcSecurityGroupIds: [this.dbSecurityGroup.id],
51
66
  storageEncrypted: true,
@@ -27,7 +27,8 @@ export type StaticSiteService = {
27
27
  export type WebServerService = {
28
28
  type: 'WEB_SERVER';
29
29
  environment?: aws.ecs.KeyValuePair[] | ((services: Services) => aws.ecs.KeyValuePair[]);
30
- } & ServiceArgs & Omit<WebServerArgs, 'cluster' | 'vpc' | 'hostedZoneId' | 'environment'>;
30
+ secrets?: aws.ecs.Secret[] | ((services: Services) => aws.ecs.Secret[]);
31
+ } & ServiceArgs & Omit<WebServerArgs, 'cluster' | 'vpc' | 'hostedZoneId' | 'environment' | 'secrets'>;
31
32
  export type ProjectArgs = {
32
33
  services: (DatabaseService | RedisService | StaticSiteService | WebServerService)[];
33
34
  hostedZoneId?: pulumi.Input<string>;
@@ -110,11 +110,12 @@ class Project extends pulumi.ComponentResource {
110
110
  return;
111
111
  if (!this.hostedZoneId)
112
112
  throw new MissingHostedZoneId(options.type);
113
- const { serviceName, environment } = options, ecsOptions = __rest(options, ["serviceName", "environment"]);
113
+ const { serviceName, environment, secrets } = options, ecsOptions = __rest(options, ["serviceName", "environment", "secrets"]);
114
114
  const parsedEnv = typeof environment === 'function'
115
115
  ? environment(this.services)
116
116
  : environment;
117
- 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 });
117
+ const parsedSecrets = typeof secrets === 'function' ? secrets(this.services) : secrets;
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, secrets: parsedSecrets }), { parent: this });
118
119
  this.services[options.serviceName] = service;
119
120
  }
120
121
  }
@@ -1,5 +1,6 @@
1
1
  import * as pulumi from '@pulumi/pulumi';
2
2
  import * as upstash from '@upstash/pulumi';
3
+ import * as aws from '@pulumi/aws';
3
4
  export type RedisArgs = {
4
5
  /**
5
6
  * Redis database name.
@@ -15,5 +16,7 @@ export interface RedisOptions extends pulumi.ComponentResourceOptions {
15
16
  }
16
17
  export declare class Redis extends pulumi.ComponentResource {
17
18
  instance: upstash.RedisDatabase;
19
+ passwordSecret: aws.secretsmanager.Secret;
20
+ username: string;
18
21
  constructor(name: string, args: RedisArgs, opts: RedisOptions);
19
22
  }
@@ -3,12 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Redis = void 0;
4
4
  const pulumi = require("@pulumi/pulumi");
5
5
  const upstash = require("@upstash/pulumi");
6
+ const aws = require("@pulumi/aws");
6
7
  const defaults = {
7
8
  region: 'us-east-1',
8
9
  };
9
10
  class Redis extends pulumi.ComponentResource {
10
11
  constructor(name, args, opts) {
11
12
  super('studion:Redis', name, {}, opts);
13
+ this.username = 'default';
14
+ const project = pulumi.getProject();
15
+ const stack = pulumi.getStack();
12
16
  const argsWithDefaults = Object.assign({}, defaults, args);
13
17
  this.instance = new upstash.RedisDatabase(name, {
14
18
  databaseName: argsWithDefaults.dbName,
@@ -16,6 +20,13 @@ class Redis extends pulumi.ComponentResource {
16
20
  eviction: true,
17
21
  tls: true,
18
22
  }, { provider: opts.provider, parent: this });
23
+ this.passwordSecret = new aws.secretsmanager.Secret(`${name}-password-secret`, {
24
+ name: `${stack}/${project}/RedisPassword`,
25
+ }, { parent: this, dependsOn: [this.instance] });
26
+ const passwordSecretValue = new aws.secretsmanager.SecretVersion(`${name}-password-secret-value`, {
27
+ secretId: this.passwordSecret.id,
28
+ secretString: this.instance.password,
29
+ }, { parent: this, dependsOn: [this.passwordSecret] });
19
30
  this.registerOutputs();
20
31
  }
21
32
  }
@@ -12,7 +12,6 @@ class StaticSite extends pulumi.ComponentResource {
12
12
  hostedZoneId: args.hostedZoneId,
13
13
  }, { parent: this });
14
14
  const bucket = new aws.s3.Bucket(`${name}-bucket`, {
15
- bucket: name,
16
15
  website: {
17
16
  indexDocument: 'index.html',
18
17
  errorDocument: 'index.html',
@@ -61,9 +61,16 @@ export type WebServerArgs = {
61
61
  */
62
62
  size?: pulumi.Input<Size>;
63
63
  /**
64
- * The environment variables to pass to a container. Defaults to [].
64
+ * The environment variables to pass to a container. Don't use this field for
65
+ * sensitive information such as passwords, API keys, etc. For that purpose,
66
+ * please use the `secrets` property.
67
+ * Defaults to [].
65
68
  */
66
69
  environment?: aws.ecs.KeyValuePair[];
70
+ /**
71
+ * The secrets to pass to the container. Defaults to [].
72
+ */
73
+ secrets?: aws.ecs.Secret[];
67
74
  /**
68
75
  * Path for the health check request. Defaults to "/healtcheck".
69
76
  */
@@ -26,6 +26,7 @@ const defaults = {
26
26
  maxCount: 10,
27
27
  size: 'small',
28
28
  environment: [],
29
+ secrets: [],
29
30
  healtCheckPath: '/healtcheck',
30
31
  taskExecutionRoleInlinePolicies: [],
31
32
  taskRoleInlinePolicies: [],
@@ -128,6 +129,20 @@ class WebServer extends pulumi.ComponentResource {
128
129
  },
129
130
  ],
130
131
  }, { parent: this });
132
+ const secretManagerSecretsInlinePolicy = {
133
+ name: `${name}-secret-manager-access`,
134
+ policy: JSON.stringify({
135
+ Version: '2012-10-17',
136
+ Statement: [
137
+ {
138
+ Sid: 'AllowContainerToGetSecretManagerSecrets',
139
+ Effect: 'Allow',
140
+ Action: ['secretsmanager:GetSecretValue'],
141
+ Resource: '*',
142
+ },
143
+ ],
144
+ }),
145
+ };
131
146
  const taskExecutionRole = new aws.iam.Role(`${name}-ecs-task-exec-role`, {
132
147
  name: `${name}-ecs-task-exec-role`,
133
148
  assumeRolePolicy,
@@ -135,7 +150,10 @@ class WebServer extends pulumi.ComponentResource {
135
150
  'arn:aws:iam::aws:policy/CloudWatchFullAccess',
136
151
  'arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess',
137
152
  ],
138
- inlinePolicies: argsWithDefaults.taskExecutionRoleInlinePolicies,
153
+ inlinePolicies: [
154
+ secretManagerSecretsInlinePolicy,
155
+ ...argsWithDefaults.taskExecutionRoleInlinePolicies,
156
+ ],
139
157
  }, { parent: this });
140
158
  const execCmdInlinePolicy = {
141
159
  name: `${name}-ecs-exec`,
@@ -191,10 +209,11 @@ class WebServer extends pulumi.ComponentResource {
191
209
  argsWithDefaults.image,
192
210
  argsWithDefaults.port,
193
211
  argsWithDefaults.environment,
212
+ argsWithDefaults.secrets,
194
213
  this.logGroup.name,
195
214
  awsRegion,
196
215
  ])
197
- .apply(([containerName, image, port, environment, logGroup, region]) => {
216
+ .apply(([containerName, image, port, environment, secrets, logGroup, region,]) => {
198
217
  return JSON.stringify([
199
218
  {
200
219
  readonlyRootFilesystem: false,
@@ -216,6 +235,7 @@ class WebServer extends pulumi.ComponentResource {
216
235
  },
217
236
  },
218
237
  environment,
238
+ secrets,
219
239
  },
220
240
  ]);
221
241
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studion/infra-code-blocks",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Studion common infra components",
5
5
  "keywords": [
6
6
  "infrastructure",