seamless-cli 0.2.0 → 0.5.0

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.
Files changed (129) hide show
  1. package/README.md +5 -6
  2. package/dist/commands/help.js +18 -1
  3. package/dist/commands/help.js.map +1 -1
  4. package/dist/commands/init.js +104 -21
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/commands/verify.js +259 -0
  7. package/dist/commands/verify.js.map +1 -0
  8. package/dist/core/bootstrapSecret.js +12 -4
  9. package/dist/core/bootstrapSecret.js.map +1 -1
  10. package/dist/core/exec.js +2 -1
  11. package/dist/core/exec.js.map +1 -1
  12. package/dist/core/fetch.js +2 -1
  13. package/dist/core/fetch.js.map +1 -1
  14. package/dist/core/images.js +11 -0
  15. package/dist/core/images.js.map +1 -0
  16. package/dist/core/oauthProviders.js +89 -0
  17. package/dist/core/oauthProviders.js.map +1 -0
  18. package/dist/core/templates.js +185 -0
  19. package/dist/core/templates.js.map +1 -0
  20. package/dist/generators/auth/auth.js +40 -19
  21. package/dist/generators/auth/auth.js.map +1 -1
  22. package/dist/generators/config/config.js +3 -4
  23. package/dist/generators/config/config.js.map +1 -1
  24. package/dist/generators/docker/docker.js +51 -31
  25. package/dist/generators/docker/docker.js.map +1 -1
  26. package/dist/index.js +11 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/prompts/oauthSetup.js +36 -0
  29. package/dist/prompts/oauthSetup.js.map +1 -0
  30. package/dist/prompts/projectSetup.js +43 -19
  31. package/dist/prompts/projectSetup.js.map +1 -1
  32. package/package.json +6 -1
  33. package/verify/adapter-app/Dockerfile +14 -0
  34. package/verify/adapter-app/package.json +13 -0
  35. package/verify/adapter-app/server.mjs +62 -0
  36. package/verify/adapter-app/vendor/.gitignore +3 -0
  37. package/verify/adapter-app/vendor/.gitkeep +0 -0
  38. package/verify/docker-compose.verify.yml +154 -0
  39. package/verify/harness/adapter/emailOtpLogin.spec.ts +12 -0
  40. package/verify/harness/adapter/magicLink.spec.ts +14 -0
  41. package/verify/harness/adapter/oauth.spec.ts +11 -0
  42. package/verify/harness/adapter/registration.spec.ts +11 -0
  43. package/verify/harness/adapter/sessionLifecycle.spec.ts +20 -0
  44. package/verify/harness/api/adminBootstrap.spec.ts +34 -0
  45. package/verify/harness/api/emailOtp.spec.ts +17 -0
  46. package/verify/harness/api/jwks.spec.ts +17 -0
  47. package/verify/harness/api/magicLink.spec.ts +36 -0
  48. package/verify/harness/api/oauth.spec.ts +12 -0
  49. package/verify/harness/api/organizations.spec.ts +28 -0
  50. package/verify/harness/api/phoneOtp.spec.ts +19 -0
  51. package/verify/harness/api/sessionLifecycle.spec.ts +26 -0
  52. package/verify/harness/api/stepUp.spec.ts +39 -0
  53. package/verify/harness/api/totp.spec.ts +24 -0
  54. package/verify/harness/global-setup.ts +45 -0
  55. package/verify/harness/lib/adapterFlows.ts +57 -0
  56. package/verify/harness/lib/client.ts +32 -0
  57. package/verify/harness/lib/env.ts +45 -0
  58. package/verify/harness/lib/fixtures.ts +20 -0
  59. package/verify/harness/lib/flows.ts +204 -0
  60. package/verify/harness/lib/matrixReporter.ts +73 -0
  61. package/verify/harness/lib/reactFlows.ts +117 -0
  62. package/verify/harness/lib/serviceToken.ts +26 -0
  63. package/verify/harness/lib/totp.ts +36 -0
  64. package/verify/harness/mock-oidc.ts +112 -0
  65. package/verify/harness/package-lock.json +93 -0
  66. package/verify/harness/package.json +14 -0
  67. package/verify/harness/playwright.config.ts +30 -0
  68. package/verify/harness/react/emailOtpLogin.spec.ts +13 -0
  69. package/verify/harness/react/logout.spec.ts +17 -0
  70. package/verify/harness/react/magicLinkLogin.spec.ts +29 -0
  71. package/verify/harness/react/oauth.spec.ts +13 -0
  72. package/verify/harness/react/passkeyLogin.spec.ts +24 -0
  73. package/verify/harness/react/passkeyRegister.spec.ts +15 -0
  74. package/verify/harness/react/register.spec.ts +18 -0
  75. package/verify/react-vendor/.gitkeep +0 -0
  76. package/dist/commands/contribute.js +0 -121
  77. package/dist/commands/contribute.js.map +0 -1
  78. package/dist/commands/deploy.js +0 -190
  79. package/dist/commands/deploy.js.map +0 -1
  80. package/dist/commands/destroy.js +0 -122
  81. package/dist/commands/destroy.js.map +0 -1
  82. package/dist/contributor/catalog.js +0 -129
  83. package/dist/contributor/catalog.js.map +0 -1
  84. package/dist/contributor/workspace.js +0 -551
  85. package/dist/contributor/workspace.js.map +0 -1
  86. package/dist/core/acm.js +0 -24
  87. package/dist/core/acm.js.map +0 -1
  88. package/dist/core/aws.js +0 -374
  89. package/dist/core/aws.js.map +0 -1
  90. package/dist/core/buildPush.js +0 -91
  91. package/dist/core/buildPush.js.map +0 -1
  92. package/dist/core/configure.js +0 -25
  93. package/dist/core/configure.js.map +0 -1
  94. package/dist/core/deployConfig.js +0 -66
  95. package/dist/core/deployConfig.js.map +0 -1
  96. package/dist/core/deployManifest.js +0 -163
  97. package/dist/core/deployManifest.js.map +0 -1
  98. package/dist/core/deploySecrets.js +0 -31
  99. package/dist/core/deploySecrets.js.map +0 -1
  100. package/dist/core/destroy.js +0 -38
  101. package/dist/core/destroy.js.map +0 -1
  102. package/dist/core/envDiscovery.js +0 -153
  103. package/dist/core/envDiscovery.js.map +0 -1
  104. package/dist/core/envRegistry.js +0 -76
  105. package/dist/core/envRegistry.js.map +0 -1
  106. package/dist/core/health.js +0 -11
  107. package/dist/core/health.js.map +0 -1
  108. package/dist/core/help.js +0 -75
  109. package/dist/core/help.js.map +0 -1
  110. package/dist/core/preReqs.js +0 -14
  111. package/dist/core/preReqs.js.map +0 -1
  112. package/dist/core/terraform.js +0 -34
  113. package/dist/core/terraform.js.map +0 -1
  114. package/dist/core/tfVars.js +0 -69
  115. package/dist/core/tfVars.js.map +0 -1
  116. package/dist/generators/backend/express.js +0 -13
  117. package/dist/generators/backend/express.js.map +0 -1
  118. package/dist/generators/deploy/aws/dev/files.js +0 -36
  119. package/dist/generators/deploy/aws/dev/files.js.map +0 -1
  120. package/dist/generators/deploy/aws/dev/template.js +0 -1240
  121. package/dist/generators/deploy/aws/dev/template.js.map +0 -1
  122. package/dist/generators/frontend/react.js +0 -13
  123. package/dist/generators/frontend/react.js.map +0 -1
  124. package/dist/prompts/contributeSetup.js +0 -39
  125. package/dist/prompts/contributeSetup.js.map +0 -1
  126. package/dist/prompts/deploySetup.js +0 -244
  127. package/dist/prompts/deploySetup.js.map +0 -1
  128. package/dist/utils/repoUtils.js +0 -23
  129. package/dist/utils/repoUtils.js.map +0 -1
@@ -1,1240 +0,0 @@
1
- function tf(value) {
2
- return value.replace(/"/g, '\\"');
3
- }
4
- function escapeTfString(value) {
5
- return value
6
- .replace(/\\/g, "\\\\")
7
- .replace(/"/g, '\\"')
8
- .replace(/\n/g, "\\n");
9
- }
10
- function renderEnvEntries(vars) {
11
- return vars
12
- .map((envVar) => `{ name = "${escapeTfString(envVar.name)}", value = "${escapeTfString(envVar.value)}" }`)
13
- .join(",\n ");
14
- }
15
- function renderUserEnvVars(vars) {
16
- return vars
17
- .filter((envVar) => envVar.target === "task_definition")
18
- .map((envVar) => ({
19
- name: envVar.key,
20
- value: envVar.value,
21
- }));
22
- }
23
- export function renderMainTf() {
24
- return `
25
- terraform {
26
- required_version = ">= 1.5.0"
27
-
28
- required_providers {
29
- aws = {
30
- source = "hashicorp/aws"
31
- version = "~> 5.0"
32
- }
33
-
34
- random = {
35
- source = "hashicorp/random"
36
- version = "~> 3.6"
37
- }
38
- }
39
- }
40
-
41
- provider "aws" {
42
- region = var.aws_region
43
- profile = var.aws_profile
44
- }
45
- `.trimStart();
46
- }
47
- export function renderVariablesTf(manifest) {
48
- return `
49
- variable "project_name" {
50
- type = string
51
- default = "${tf(manifest.projectName)}"
52
- }
53
-
54
- variable "aws_region" {
55
- type = string
56
- default = "${tf(manifest.region)}"
57
- }
58
-
59
- variable "aws_profile" {
60
- type = string
61
- default = "${tf(manifest.awsProfile)}"
62
- }
63
-
64
- variable "root_domain" {
65
- type = string
66
- default = "${tf(manifest.domain.root)}"
67
- }
68
-
69
- variable "hosted_zone_domain" {
70
- type = string
71
- default = "${tf(manifest.domain.hostedZoneDomain ?? manifest.domain.root)}"
72
- }
73
-
74
- variable "web_host" {
75
- type = string
76
- default = "${tf(manifest.domain.webHost)}"
77
- }
78
-
79
- variable "api_host" {
80
- type = string
81
- default = "${tf(manifest.domain.apiHost)}"
82
- }
83
-
84
- variable "auth_host" {
85
- type = string
86
- default = "${tf(manifest.domain.authHost)}"
87
- }
88
-
89
- variable "admin_host" {
90
- type = string
91
- default = "${tf(manifest.domain.adminHost)}"
92
- }
93
-
94
- variable "backup_enabled" {
95
- type = bool
96
- default = ${manifest.backup.enabled ? "true" : "false"}
97
- }
98
-
99
- variable "backup_retention_days" {
100
- type = number
101
- default = ${manifest.backup.retentionDays}
102
- }
103
-
104
- variable "web_image" {
105
- type = string
106
- default = "REPLACE_WEB_IMAGE_URI"
107
- }
108
-
109
- variable "api_image" {
110
- type = string
111
- default = "REPLACE_API_IMAGE_URI"
112
- }
113
-
114
- variable "auth_image" {
115
- type = string
116
- default = "REPLACE_AUTH_IMAGE_URI"
117
- }
118
-
119
- variable "admin_image" {
120
- type = string
121
- default = "REPLACE_ADMIN_IMAGE_URI"
122
- }
123
-
124
- variable "postgres_image" {
125
- type = string
126
- default = "REPLACE_POSTGRES_IMAGE_URI"
127
- }
128
-
129
- variable "ecs_instance_type" {
130
- type = string
131
- default = "t3.large"
132
- }
133
-
134
- variable "desired_capacity" {
135
- type = number
136
- default = 1
137
- }
138
-
139
- variable "api_origin" {
140
- type = string
141
- }
142
-
143
- variable "auth_issuer" {
144
- type = string
145
- }
146
-
147
- variable "web_origin" {
148
- type = string
149
- }
150
-
151
- variable "admin_origin" {
152
- type = string
153
- }
154
-
155
- variable "rpid" {
156
- type = string
157
- }
158
-
159
- variable "api_service_token" {
160
- type = string
161
- sensitive = true
162
- }
163
-
164
- variable "cookie_signing_key" {
165
- type = string
166
- sensitive = true
167
- }
168
-
169
- variable "bootstrap_secret" {
170
- type = string
171
- sensitive = true
172
- }
173
-
174
- variable "auth_db_user" {
175
- type = string
176
- }
177
-
178
- variable "auth_db_password" {
179
- type = string
180
- sensitive = true
181
- }
182
-
183
- variable "auth_db_name" {
184
- type = string
185
- }
186
-
187
- variable "api_db_user" {
188
- type = string
189
- }
190
-
191
- variable "api_db_password" {
192
- type = string
193
- sensitive = true
194
- }
195
-
196
- variable "api_db_name" {
197
- type = string
198
- }
199
-
200
- variable "jwks_active_kid" {
201
- type = string
202
- }
203
-
204
- variable "seamless_jwks_active_kid" {
205
- type = string
206
- }
207
-
208
- variable "jwks_private_key" {
209
- type = string
210
- sensitive = true
211
- }
212
-
213
- variable "jwks_public_keys" {
214
- type = string
215
- sensitive = true
216
- }
217
-
218
- locals {
219
- app_slug = replace(lower(var.project_name), " ", "-")
220
- common_tags = {
221
- Project = var.project_name
222
- Tier = "dev"
223
- Managed = "seamless-cli"
224
- }
225
- }
226
- `.trimStart();
227
- }
228
- export function renderNetworkingTf() {
229
- return `
230
- resource "aws_vpc" "main" {
231
- cidr_block = "10.40.0.0/16"
232
- enable_dns_hostnames = true
233
- enable_dns_support = true
234
-
235
- tags = merge(local.common_tags, {
236
- Name = "\${local.app_slug}-vpc"
237
- })
238
- }
239
-
240
- resource "aws_internet_gateway" "main" {
241
- vpc_id = aws_vpc.main.id
242
-
243
- tags = merge(local.common_tags, {
244
- Name = "\${local.app_slug}-igw"
245
- })
246
- }
247
-
248
- resource "aws_subnet" "public_a" {
249
- vpc_id = aws_vpc.main.id
250
- cidr_block = "10.40.1.0/24"
251
- map_public_ip_on_launch = true
252
- availability_zone = "\${var.aws_region}a"
253
-
254
- tags = merge(local.common_tags, {
255
- Name = "\${local.app_slug}-public-a"
256
- })
257
- }
258
-
259
- resource "aws_subnet" "public_b" {
260
- vpc_id = aws_vpc.main.id
261
- cidr_block = "10.40.2.0/24"
262
- map_public_ip_on_launch = true
263
- availability_zone = "\${var.aws_region}b"
264
-
265
- tags = merge(local.common_tags, {
266
- Name = "\${local.app_slug}-public-b"
267
- })
268
- }
269
-
270
- resource "aws_route_table" "public" {
271
- vpc_id = aws_vpc.main.id
272
-
273
- tags = merge(local.common_tags, {
274
- Name = "\${local.app_slug}-public-rt"
275
- })
276
- }
277
-
278
- resource "aws_route" "internet_access" {
279
- route_table_id = aws_route_table.public.id
280
- destination_cidr_block = "0.0.0.0/0"
281
- gateway_id = aws_internet_gateway.main.id
282
- }
283
-
284
- resource "aws_route_table_association" "public_a" {
285
- subnet_id = aws_subnet.public_a.id
286
- route_table_id = aws_route_table.public.id
287
- }
288
-
289
- resource "aws_route_table_association" "public_b" {
290
- subnet_id = aws_subnet.public_b.id
291
- route_table_id = aws_route_table.public.id
292
- }
293
- `.trimStart();
294
- }
295
- export function renderRoute53Tf() {
296
- return `
297
- data "aws_route53_zone" "main" {
298
- name = var.hosted_zone_domain
299
- private_zone = false
300
- }
301
-
302
- resource "aws_route53_record" "web" {
303
- zone_id = data.aws_route53_zone.main.zone_id
304
- name = var.web_host
305
- type = "A"
306
-
307
- alias {
308
- name = aws_lb.main.dns_name
309
- zone_id = aws_lb.main.zone_id
310
- evaluate_target_health = true
311
- }
312
- }
313
-
314
- resource "aws_route53_record" "api" {
315
- zone_id = data.aws_route53_zone.main.zone_id
316
- name = var.api_host
317
- type = "A"
318
-
319
- alias {
320
- name = aws_lb.main.dns_name
321
- zone_id = aws_lb.main.zone_id
322
- evaluate_target_health = true
323
- }
324
- }
325
-
326
- resource "aws_route53_record" "auth" {
327
- zone_id = data.aws_route53_zone.main.zone_id
328
- name = var.auth_host
329
- type = "A"
330
-
331
- alias {
332
- name = aws_lb.main.dns_name
333
- zone_id = aws_lb.main.zone_id
334
- evaluate_target_health = true
335
- }
336
- }
337
-
338
- resource "aws_route53_record" "admin" {
339
- zone_id = data.aws_route53_zone.main.zone_id
340
- name = var.admin_host
341
- type = "A"
342
-
343
- alias {
344
- name = aws_lb.main.dns_name
345
- zone_id = aws_lb.main.zone_id
346
- evaluate_target_health = true
347
- }
348
- }
349
- `.trimStart();
350
- }
351
- export function renderAcmTf() {
352
- return `
353
- variable "acm_certificate_arn" {
354
- type = string
355
- }
356
- `.trimStart();
357
- }
358
- export function renderAlbTf() {
359
- return `
360
- resource "aws_security_group" "alb" {
361
- name = "\${local.app_slug}-alb-sg"
362
- description = "ALB security group"
363
- vpc_id = aws_vpc.main.id
364
-
365
- ingress {
366
- from_port = 80
367
- to_port = 80
368
- protocol = "tcp"
369
- cidr_blocks = ["0.0.0.0/0"]
370
- }
371
-
372
- ingress {
373
- from_port = 443
374
- to_port = 443
375
- protocol = "tcp"
376
- cidr_blocks = ["0.0.0.0/0"]
377
- }
378
-
379
- egress {
380
- from_port = 0
381
- to_port = 0
382
- protocol = "-1"
383
- cidr_blocks = ["0.0.0.0/0"]
384
- }
385
-
386
- tags = local.common_tags
387
- }
388
-
389
- resource "aws_lb" "main" {
390
- name = substr("\${local.app_slug}-alb", 0, 32)
391
- internal = false
392
- load_balancer_type = "application"
393
- security_groups = [aws_security_group.alb.id]
394
- subnets = [aws_subnet.public_a.id, aws_subnet.public_b.id]
395
-
396
- tags = local.common_tags
397
- }
398
-
399
- resource "aws_lb_target_group" "web" {
400
- name = substr("\${local.app_slug}-web", 0, 32)
401
- port = 80
402
- protocol = "HTTP"
403
- target_type = "instance"
404
- vpc_id = aws_vpc.main.id
405
-
406
- health_check {
407
- path = "/"
408
- }
409
-
410
- tags = local.common_tags
411
- }
412
-
413
- resource "aws_lb_target_group" "api" {
414
- name = substr("\${local.app_slug}-api", 0, 32)
415
- port = 3000
416
- protocol = "HTTP"
417
- target_type = "instance"
418
- vpc_id = aws_vpc.main.id
419
-
420
- health_check {
421
- path = "/health"
422
- }
423
-
424
- tags = local.common_tags
425
- }
426
-
427
- resource "aws_lb_target_group" "admin" {
428
- name = substr("\${local.app_slug}-admin", 0, 32)
429
- port = 80
430
- protocol = "HTTP"
431
- target_type = "instance"
432
- vpc_id = aws_vpc.main.id
433
-
434
- health_check {
435
- path = "/"
436
- }
437
-
438
- tags = local.common_tags
439
- }
440
-
441
- resource "aws_lb_target_group" "auth" {
442
- name = substr("\${local.app_slug}-auth", 0, 32)
443
- port = 5312
444
- protocol = "HTTP"
445
- target_type = "instance"
446
- vpc_id = aws_vpc.main.id
447
-
448
- health_check {
449
- path = "/health/status"
450
- }
451
-
452
- tags = local.common_tags
453
- }
454
-
455
- resource "aws_lb_listener" "http" {
456
- load_balancer_arn = aws_lb.main.arn
457
- port = 80
458
- protocol = "HTTP"
459
-
460
- default_action {
461
- type = "redirect"
462
-
463
- redirect {
464
- port = "443"
465
- protocol = "HTTPS"
466
- status_code = "HTTP_301"
467
- }
468
- }
469
- }
470
-
471
- resource "aws_lb_listener" "https" {
472
- load_balancer_arn = aws_lb.main.arn
473
- port = 443
474
- protocol = "HTTPS"
475
- certificate_arn = var.acm_certificate_arn
476
- ssl_policy = "ELBSecurityPolicy-2016-08"
477
-
478
- default_action {
479
- type = "forward"
480
- target_group_arn = aws_lb_target_group.web.arn
481
- }
482
- }
483
-
484
- resource "aws_lb_listener_rule" "api" {
485
- listener_arn = aws_lb_listener.https.arn
486
- priority = 10
487
-
488
- condition {
489
- host_header {
490
- values = [var.api_host]
491
- }
492
- }
493
-
494
- action {
495
- type = "forward"
496
- target_group_arn = aws_lb_target_group.api.arn
497
- }
498
- }
499
-
500
- resource "aws_lb_listener_rule" "auth" {
501
- listener_arn = aws_lb_listener.https.arn
502
- priority = 20
503
-
504
- condition {
505
- host_header {
506
- values = [var.auth_host]
507
- }
508
- }
509
-
510
- action {
511
- type = "forward"
512
- target_group_arn = aws_lb_target_group.auth.arn
513
- }
514
- }
515
-
516
- resource "aws_lb_listener_rule" "admin" {
517
- listener_arn = aws_lb_listener.https.arn
518
- priority = 30
519
-
520
- condition {
521
- host_header {
522
- values = [var.admin_host]
523
- }
524
- }
525
-
526
- action {
527
- type = "forward"
528
- target_group_arn = aws_lb_target_group.admin.arn
529
- }
530
- }
531
- `.trimStart();
532
- }
533
- export function renderLogsTf(manifest) {
534
- return `
535
- resource "aws_cloudwatch_log_group" "web" {
536
- name = "/${tf(manifest.projectName)}/dev/web"
537
- retention_in_days = 14
538
- tags = local.common_tags
539
- }
540
-
541
- resource "aws_cloudwatch_log_group" "api" {
542
- name = "/${tf(manifest.projectName)}/dev/api"
543
- retention_in_days = 14
544
- tags = local.common_tags
545
- }
546
-
547
- resource "aws_cloudwatch_log_group" "auth" {
548
- name = "/${tf(manifest.projectName)}/dev/auth"
549
- retention_in_days = 14
550
- tags = local.common_tags
551
- }
552
-
553
- resource "aws_cloudwatch_log_group" "admin" {
554
- name = "/${tf(manifest.projectName)}/dev/admin"
555
- retention_in_days = 14
556
- tags = local.common_tags
557
- }
558
-
559
- resource "aws_cloudwatch_log_group" "postgres" {
560
- name = "/${tf(manifest.projectName)}/dev/postgres"
561
- retention_in_days = 14
562
- tags = local.common_tags
563
- }
564
-
565
- resource "aws_cloudwatch_log_group" "backup" {
566
- name = "/${tf(manifest.projectName)}/dev/backup"
567
- retention_in_days = 14
568
- tags = local.common_tags
569
- }
570
- `.trimStart();
571
- }
572
- export function renderS3Tf() {
573
- return `
574
- resource "random_id" "bucket_suffix" {
575
- byte_length = 4
576
- }
577
-
578
- resource "aws_s3_bucket" "backups" {
579
- bucket = "\${local.app_slug}-db-backups-\${random_id.bucket_suffix.hex}"
580
-
581
- tags = local.common_tags
582
- }
583
-
584
- resource "aws_s3_bucket_versioning" "backups" {
585
- bucket = aws_s3_bucket.backups.id
586
-
587
- versioning_configuration {
588
- status = "Enabled"
589
- }
590
- }
591
-
592
- resource "aws_s3_bucket_lifecycle_configuration" "backups" {
593
- bucket = aws_s3_bucket.backups.id
594
-
595
- rule {
596
- id = "expire-old-backups"
597
- status = "Enabled"
598
-
599
- filter {}
600
-
601
- expiration {
602
- days = var.backup_retention_days
603
- }
604
- }
605
- }
606
- `.trimStart();
607
- }
608
- export function renderIamTf() {
609
- return `
610
- resource "aws_iam_role" "ecs_task_execution" {
611
- name = "\${local.app_slug}-ecs-task-execution-role"
612
-
613
- assume_role_policy = jsonencode({
614
- Version = "2012-10-17",
615
- Statement = [{
616
- Action = "sts:AssumeRole",
617
- Effect = "Allow",
618
- Principal = {
619
- Service = "ecs-tasks.amazonaws.com"
620
- }
621
- }]
622
- })
623
-
624
- tags = local.common_tags
625
- }
626
-
627
- resource "aws_iam_role_policy_attachment" "ecs_task_execution_default" {
628
- role = aws_iam_role.ecs_task_execution.name
629
- policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
630
- }
631
-
632
- resource "aws_iam_role" "ecs_task_role" {
633
- name = "\${local.app_slug}-ecs-task-role"
634
-
635
- assume_role_policy = jsonencode({
636
- Version = "2012-10-17",
637
- Statement = [{
638
- Action = "sts:AssumeRole",
639
- Effect = "Allow",
640
- Principal = {
641
- Service = "ecs-tasks.amazonaws.com"
642
- }
643
- }]
644
- })
645
-
646
- tags = local.common_tags
647
- }
648
-
649
- resource "aws_iam_policy" "backup_s3" {
650
- name = "\${local.app_slug}-backup-s3-policy"
651
-
652
- policy = jsonencode({
653
- Version = "2012-10-17",
654
- Statement = [
655
- {
656
- Effect = "Allow",
657
- Action = [
658
- "s3:PutObject",
659
- "s3:ListBucket"
660
- ],
661
- Resource = [
662
- aws_s3_bucket.backups.arn,
663
- "\${aws_s3_bucket.backups.arn}/*"
664
- ]
665
- }
666
- ]
667
- })
668
- }
669
-
670
- resource "aws_iam_role_policy_attachment" "ecs_task_backup_s3" {
671
- role = aws_iam_role.ecs_task_role.name
672
- policy_arn = aws_iam_policy.backup_s3.arn
673
- }
674
-
675
- resource "aws_iam_role" "ecs_instance_role" {
676
- name = "\${local.app_slug}-ecs-instance-role"
677
-
678
- assume_role_policy = jsonencode({
679
- Version = "2012-10-17",
680
- Statement = [{
681
- Action = "sts:AssumeRole",
682
- Effect = "Allow",
683
- Principal = {
684
- Service = "ec2.amazonaws.com"
685
- }
686
- }]
687
- })
688
-
689
- tags = local.common_tags
690
- }
691
-
692
- resource "aws_iam_role_policy_attachment" "ecs_instance_ec2_container" {
693
- role = aws_iam_role.ecs_instance_role.name
694
- policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
695
- }
696
-
697
- resource "aws_iam_role_policy_attachment" "ecs_instance_ssm" {
698
- role = aws_iam_role.ecs_instance_role.name
699
- policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
700
- }
701
-
702
- resource "aws_iam_instance_profile" "ecs" {
703
- name = "\${local.app_slug}-ecs-instance-profile"
704
- role = aws_iam_role.ecs_instance_role.name
705
- }
706
- `.trimStart();
707
- }
708
- export function renderEc2Tf() {
709
- return `
710
- data "aws_ssm_parameter" "ecs_optimized_ami" {
711
- name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id"
712
- }
713
-
714
- resource "aws_security_group" "ecs_host" {
715
- name = "\${local.app_slug}-ecs-host-sg"
716
- description = "ECS host security group"
717
- vpc_id = aws_vpc.main.id
718
-
719
- ingress {
720
- from_port = 32768
721
- to_port = 65535
722
- protocol = "tcp"
723
- security_groups = [aws_security_group.alb.id]
724
- }
725
-
726
- ingress {
727
- from_port = 80
728
- to_port = 80
729
- protocol = "tcp"
730
- security_groups = [aws_security_group.alb.id]
731
- }
732
-
733
- ingress {
734
- from_port = 3001
735
- to_port = 3001
736
- protocol = "tcp"
737
- security_groups = [aws_security_group.alb.id]
738
- }
739
-
740
- ingress {
741
- from_port = 4173
742
- to_port = 4173
743
- protocol = "tcp"
744
- security_groups = [aws_security_group.alb.id]
745
- }
746
-
747
- ingress {
748
- from_port = 5312
749
- to_port = 5312
750
- protocol = "tcp"
751
- security_groups = [aws_security_group.alb.id]
752
- }
753
-
754
- egress {
755
- from_port = 0
756
- to_port = 0
757
- protocol = "-1"
758
- cidr_blocks = ["0.0.0.0/0"]
759
- }
760
-
761
- tags = local.common_tags
762
- }
763
-
764
- resource "aws_launch_template" "ecs" {
765
- name_prefix = "\${local.app_slug}-ecs-"
766
- image_id = data.aws_ssm_parameter.ecs_optimized_ami.value
767
- instance_type = var.ecs_instance_type
768
-
769
- iam_instance_profile {
770
- arn = aws_iam_instance_profile.ecs.arn
771
- }
772
-
773
- network_interfaces {
774
- associate_public_ip_address = true
775
- security_groups = [aws_security_group.ecs_host.id]
776
- }
777
-
778
- user_data = base64encode(<<-EOF
779
- #!/bin/bash
780
- echo ECS_CLUSTER=\${aws_ecs_cluster.main.name} >> /etc/ecs/ecs.config
781
- EOF
782
- )
783
-
784
- tag_specifications {
785
- resource_type = "instance"
786
-
787
- tags = merge(local.common_tags, {
788
- Name = "\${local.app_slug}-ecs-instance"
789
- })
790
- }
791
-
792
- tags = local.common_tags
793
- }
794
-
795
- resource "aws_autoscaling_group" "ecs" {
796
- name = "\${local.app_slug}-ecs-asg"
797
- vpc_zone_identifier = [aws_subnet.public_a.id]
798
- desired_capacity = var.desired_capacity
799
- min_size = 1
800
- max_size = 1
801
- health_check_type = "EC2"
802
-
803
- launch_template {
804
- id = aws_launch_template.ecs.id
805
- version = "$Latest"
806
- }
807
-
808
- tag {
809
- key = "Name"
810
- value = "\${local.app_slug}-ecs-instance"
811
- propagate_at_launch = true
812
- }
813
-
814
- tag {
815
- key = "Project"
816
- value = var.project_name
817
- propagate_at_launch = true
818
- }
819
- }
820
- `.trimStart();
821
- }
822
- export function renderEcsTf(manifest) {
823
- const webEnv = [
824
- { name: "API_URL", value: "${var.api_origin}" },
825
- ...renderUserEnvVars(manifest.env.web),
826
- ];
827
- const apiEnv = [
828
- { name: "AUTH_SERVER_URL", value: "http://127.0.0.1:5312" },
829
- { name: "APP_ORIGINS", value: "${var.api_origin}" },
830
- { name: "UI_ORIGINS", value: "${var.web_origin},${var.admin_origin}" },
831
- { name: "COOKIE_SIGNING_KEY", value: "${var.cookie_signing_key}" },
832
- { name: "API_SERVICE_TOKEN", value: "${var.api_service_token}" },
833
- { name: "JWKS_KID", value: "${var.jwks_active_kid}" },
834
- { name: "DB_HOST", value: "127.0.0.1" },
835
- { name: "DB_PORT", value: "5432" },
836
- { name: "DB_USER", value: "${var.api_db_user}" },
837
- { name: "DB_PASSWORD", value: "${var.api_db_password}" },
838
- { name: "DB_NAME", value: "${var.api_db_name}" },
839
- { name: "SQL_LOGGING", value: "false" },
840
- ...renderUserEnvVars(manifest.env.api),
841
- ];
842
- const authEnv = [
843
- { name: "NODE_ENV", value: "production" },
844
- { name: "APP_NAME", value: "${var.project_name}" },
845
- { name: "APP_ID", value: "${local.app_slug}-dev" },
846
- { name: "APP_ORIGINS", value: "${var.api_origin}" },
847
- { name: "ISSUER", value: "${var.auth_issuer}" },
848
- { name: "AUTH_MODE", value: "server" },
849
- { name: "DEMO", value: "false" },
850
- { name: "DEFAULT_ROLES", value: "user,betaUser" },
851
- { name: "AVAILABLE_ROLES", value: "user,admin,betaUser,team" },
852
- { name: "DB_LOGGING", value: "false" },
853
- { name: "DB_HOST", value: "127.0.0.1" },
854
- { name: "DB_PORT", value: "5432" },
855
- { name: "DB_NAME", value: "${var.auth_db_name}" },
856
- { name: "DB_USER", value: "${var.auth_db_user}" },
857
- { name: "DB_PASSWORD", value: "${var.auth_db_password}" },
858
- { name: "ACCESS_TOKEN_TTL", value: "30m" },
859
- { name: "REFRESH_TOKEN_TTL", value: "1h" },
860
- { name: "RATE_LIMIT", value: "100" },
861
- { name: "DELAY_AFTER", value: "50" },
862
- { name: "API_SERVICE_TOKEN", value: "${var.api_service_token}" },
863
- { name: "JWKS_ACTIVE_KID", value: "${var.jwks_active_kid}" },
864
- {
865
- name: "SEAMLESS_JWKS_ACTIVE_KID",
866
- value: "${var.seamless_jwks_active_kid}",
867
- },
868
- {
869
- name: "SEAMLESS_JWKS_KEY_main_PRIVATE",
870
- value: "${var.jwks_private_key}",
871
- },
872
- { name: "JWKS_PUBLIC_KEYS", value: "${var.jwks_public_keys}" },
873
- { name: "RPID", value: "${var.rpid}" },
874
- { name: "ORIGINS", value: "${var.web_origin},${var.admin_origin}" },
875
- { name: "SEAMLESS_BOOTSTRAP_ENABLED", value: "true" },
876
- { name: "SEAMLESS_BOOTSTRAP_SECRET", value: "${var.bootstrap_secret}" },
877
- ...renderUserEnvVars(manifest.env.auth),
878
- ];
879
- const adminEnv = [
880
- { name: "API_URL", value: "${var.api_origin}" },
881
- { name: "AUTH_MODE", value: "server" },
882
- ...renderUserEnvVars(manifest.env.admin),
883
- ];
884
- return `
885
- resource "aws_ecs_cluster" "main" {
886
- name = "\${local.app_slug}-cluster"
887
- tags = local.common_tags
888
- }
889
-
890
- resource "aws_ecs_task_definition" "app" {
891
- family = "\${local.app_slug}-app"
892
- network_mode = "bridge"
893
- requires_compatibilities = ["EC2"]
894
- cpu = "1024"
895
- memory = "3584"
896
- execution_role_arn = aws_iam_role.ecs_task_execution.arn
897
- task_role_arn = aws_iam_role.ecs_task_role.arn
898
-
899
- container_definitions = jsonencode([
900
- {
901
- name = "web"
902
- image = var.web_image
903
- essential = true
904
- memoryReservation = 128
905
- environment = [
906
- ${renderEnvEntries(webEnv)}
907
- ]
908
- portMappings = [
909
- {
910
- containerPort = 80
911
- hostPort = 0
912
- protocol = "tcp"
913
- }
914
- ]
915
- logConfiguration = {
916
- logDriver = "awslogs"
917
- options = {
918
- awslogs-group = aws_cloudwatch_log_group.web.name
919
- awslogs-region = var.aws_region
920
- awslogs-stream-prefix = "web"
921
- }
922
- }
923
- },
924
- {
925
- name = "api"
926
- image = var.api_image
927
- essential = true
928
- memoryReservation = 512
929
- environment = [
930
- ${renderEnvEntries(apiEnv)}
931
- ]
932
- portMappings = [
933
- {
934
- containerPort = 3000
935
- hostPort = 0
936
- protocol = "tcp"
937
- }
938
- ]
939
- logConfiguration = {
940
- logDriver = "awslogs"
941
- options = {
942
- awslogs-group = aws_cloudwatch_log_group.api.name
943
- awslogs-region = var.aws_region
944
- awslogs-stream-prefix = "api"
945
- }
946
- }
947
- },
948
- {
949
- name = "auth"
950
- image = var.auth_image
951
- essential = true
952
- memoryReservation = 512
953
- environment = [
954
- ${renderEnvEntries(authEnv)}
955
- ]
956
- portMappings = [
957
- {
958
- containerPort = 5312
959
- hostPort = 0
960
- protocol = "tcp"
961
- }
962
- ]
963
- logConfiguration = {
964
- logDriver = "awslogs"
965
- options = {
966
- awslogs-group = aws_cloudwatch_log_group.auth.name
967
- awslogs-region = var.aws_region
968
- awslogs-stream-prefix = "auth"
969
- }
970
- }
971
- },
972
- {
973
- name = "admin"
974
- image = var.admin_image
975
- essential = true
976
- memoryReservation = 128
977
- environment = [
978
- ${renderEnvEntries(adminEnv)}
979
- ]
980
- portMappings = [
981
- {
982
- containerPort = 80
983
- hostPort = 0
984
- protocol = "tcp"
985
- }
986
- ]
987
- logConfiguration = {
988
- logDriver = "awslogs"
989
- options = {
990
- awslogs-group = aws_cloudwatch_log_group.admin.name
991
- awslogs-region = var.aws_region
992
- awslogs-stream-prefix = "admin"
993
- }
994
- }
995
- },
996
- {
997
- name = "postgres"
998
- image = var.postgres_image
999
- essential = true
1000
- memoryReservation = 1024
1001
- environment = [
1002
- { name = "POSTGRES_USER", value = "postgres" },
1003
- { name = "POSTGRES_PASSWORD", value = "postgres-dev-root-password" }
1004
- ]
1005
- portMappings = [
1006
- {
1007
- containerPort = 5432
1008
- hostPort = 5432
1009
- protocol = "tcp"
1010
- }
1011
- ]
1012
- logConfiguration = {
1013
- logDriver = "awslogs"
1014
- options = {
1015
- awslogs-group = aws_cloudwatch_log_group.postgres.name
1016
- awslogs-region = var.aws_region
1017
- awslogs-stream-prefix = "postgres"
1018
- }
1019
- }
1020
- mountPoints = [
1021
- {
1022
- sourceVolume = "postgres_data"
1023
- containerPath = "/var/lib/postgresql/data"
1024
- readOnly = false
1025
- }
1026
- ]
1027
- }
1028
- ])
1029
-
1030
- volume {
1031
- name = "postgres_data"
1032
- }
1033
-
1034
- tags = local.common_tags
1035
- }
1036
-
1037
- resource "aws_ecs_service" "app" {
1038
- name = "\${local.app_slug}-service"
1039
- cluster = aws_ecs_cluster.main.id
1040
- task_definition = aws_ecs_task_definition.app.arn
1041
- desired_count = 1
1042
- launch_type = "EC2"
1043
-
1044
- load_balancer {
1045
- target_group_arn = aws_lb_target_group.web.arn
1046
- container_name = "web"
1047
- container_port = 80
1048
- }
1049
-
1050
- load_balancer {
1051
- target_group_arn = aws_lb_target_group.api.arn
1052
- container_name = "api"
1053
- container_port = 3000
1054
- }
1055
-
1056
- load_balancer {
1057
- target_group_arn = aws_lb_target_group.auth.arn
1058
- container_name = "auth"
1059
- container_port = 5312
1060
- }
1061
-
1062
- load_balancer {
1063
- target_group_arn = aws_lb_target_group.admin.arn
1064
- container_name = "admin"
1065
- container_port = 80
1066
- }
1067
-
1068
- depends_on = [
1069
- aws_lb_listener.https,
1070
- aws_autoscaling_group.ecs
1071
- ]
1072
- }
1073
- `.trimStart();
1074
- }
1075
- export function renderBackupTf() {
1076
- return `
1077
- locals {
1078
- backup_schedule_expression = (
1079
- var.backup_enabled == false ? null :
1080
- "cron(0 5 * * ? *)"
1081
- )
1082
-
1083
- backup_command = "export PGPASSWORD=postgres-dev-root-password && mkdir -p /tmp/backups && pg_dump -h 127.0.0.1 -U postgres -d postgres | gzip > /tmp/backups/backup.sql.gz && apt-get update >/dev/null 2>&1 && apt-get install -y awscli >/dev/null 2>&1 && aws s3 cp /tmp/backups/backup.sql.gz s3://\${aws_s3_bucket.backups.bucket}/postgres/dev/backup-$(date +%Y%m%d%H%M%S).sql.gz"
1084
- }
1085
-
1086
- resource "aws_ecs_task_definition" "backup" {
1087
- count = var.backup_enabled ? 1 : 0
1088
- family = "\${local.app_slug}-backup"
1089
- network_mode = "bridge"
1090
- requires_compatibilities = ["EC2"]
1091
- cpu = "128"
1092
- memory = "256"
1093
- execution_role_arn = aws_iam_role.ecs_task_execution.arn
1094
- task_role_arn = aws_iam_role.ecs_task_role.arn
1095
-
1096
- container_definitions = jsonencode([
1097
- {
1098
- name = "backup"
1099
- image = "postgres:16"
1100
- essential = true
1101
- entryPoint = ["/bin/sh", "-lc"]
1102
- command = [local.backup_command]
1103
- logConfiguration = {
1104
- logDriver = "awslogs"
1105
- options = {
1106
- awslogs-group = aws_cloudwatch_log_group.backup.name
1107
- awslogs-region = var.aws_region
1108
- awslogs-stream-prefix = "backup"
1109
- }
1110
- }
1111
- }
1112
- ])
1113
-
1114
- tags = local.common_tags
1115
- }
1116
-
1117
- resource "aws_cloudwatch_event_rule" "backup" {
1118
- count = var.backup_enabled ? 1 : 0
1119
- name = "\${local.app_slug}-backup-schedule"
1120
- schedule_expression = local.backup_schedule_expression
1121
- }
1122
-
1123
- resource "aws_iam_role" "events_invoke_ecs" {
1124
- count = var.backup_enabled ? 1 : 0
1125
- name = "\${local.app_slug}-events-invoke-ecs-role"
1126
-
1127
- assume_role_policy = jsonencode({
1128
- Version = "2012-10-17",
1129
- Statement = [{
1130
- Effect = "Allow",
1131
- Principal = { Service = "events.amazonaws.com" },
1132
- Action = "sts:AssumeRole"
1133
- }]
1134
- })
1135
-
1136
- tags = local.common_tags
1137
- }
1138
-
1139
- resource "aws_iam_role_policy" "events_invoke_ecs" {
1140
- count = var.backup_enabled ? 1 : 0
1141
- name = "\${local.app_slug}-events-invoke-ecs-policy"
1142
- role = aws_iam_role.events_invoke_ecs[0].id
1143
-
1144
- policy = jsonencode({
1145
- Version = "2012-10-17",
1146
- Statement = [
1147
- {
1148
- Effect = "Allow",
1149
- Action = ["ecs:RunTask"],
1150
- Resource = [aws_ecs_task_definition.backup[0].arn]
1151
- },
1152
- {
1153
- Effect = "Allow",
1154
- Action = ["iam:PassRole"],
1155
- Resource = [
1156
- aws_iam_role.ecs_task_execution.arn,
1157
- aws_iam_role.ecs_task_role.arn
1158
- ]
1159
- }
1160
- ]
1161
- })
1162
- }
1163
-
1164
- resource "aws_cloudwatch_event_target" "backup" {
1165
- count = var.backup_enabled ? 1 : 0
1166
- rule = aws_cloudwatch_event_rule.backup[0].name
1167
- arn = aws_ecs_cluster.main.arn
1168
- role_arn = aws_iam_role.events_invoke_ecs[0].arn
1169
-
1170
- ecs_target {
1171
- launch_type = "EC2"
1172
- task_definition_arn = aws_ecs_task_definition.backup[0].arn
1173
- task_count = 1
1174
- }
1175
- }
1176
- `.trimStart();
1177
- }
1178
- export function renderOutputsTf() {
1179
- return `
1180
- output "web_url" {
1181
- value = "https://\${var.web_host}"
1182
- }
1183
-
1184
- output "api_url" {
1185
- value = "https://\${var.api_host}"
1186
- }
1187
-
1188
- output "auth_url" {
1189
- value = "https://\${var.auth_host}"
1190
- }
1191
-
1192
- output "admin_url" {
1193
- value = "https://\${var.admin_host}"
1194
- }
1195
-
1196
- output "backup_bucket_name" {
1197
- value = aws_s3_bucket.backups.bucket
1198
- }
1199
- `.trimStart();
1200
- }
1201
- export function renderPostgresInitSql(manifest) {
1202
- const authUser = manifest.runtime.secrets.authDbUser.replace(/'/g, "''");
1203
- const authPassword = manifest.runtime.secrets.authDbPassword.replace(/'/g, "''");
1204
- const authDbName = manifest.runtime.secrets.authDbName.replace(/'/g, "''");
1205
- const apiUser = manifest.runtime.secrets.apiDbUser.replace(/'/g, "''");
1206
- const apiPassword = manifest.runtime.secrets.apiDbPassword.replace(/'/g, "''");
1207
- const apiDbName = manifest.runtime.secrets.apiDbName.replace(/'/g, "''");
1208
- return `
1209
- DO
1210
- $$
1211
- BEGIN
1212
- IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${authUser}') THEN
1213
- CREATE ROLE ${authUser} LOGIN PASSWORD '${authPassword}';
1214
- END IF;
1215
- END
1216
- $$;
1217
-
1218
- DO
1219
- $$
1220
- BEGIN
1221
- IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${apiUser}') THEN
1222
- CREATE ROLE ${apiUser} LOGIN PASSWORD '${apiPassword}';
1223
- END IF;
1224
- END
1225
- $$;
1226
-
1227
- CREATE DATABASE ${authDbName} OWNER ${authUser};
1228
- CREATE DATABASE ${apiDbName} OWNER ${apiUser};
1229
-
1230
- GRANT ALL PRIVILEGES ON DATABASE ${authDbName} TO ${authUser};
1231
- GRANT ALL PRIVILEGES ON DATABASE ${apiDbName} TO ${apiUser};
1232
- `.trimStart();
1233
- }
1234
- export function renderPostgresDockerfile() {
1235
- return `
1236
- FROM postgres:16
1237
- COPY init.sql /docker-entrypoint-initdb.d/001-init.sql
1238
- `.trimStart();
1239
- }
1240
- //# sourceMappingURL=template.js.map