@build-astron-co/nimbus 0.4.1 → 0.4.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/CHANGELOG.md +268 -89
- package/README.md +26 -567
- package/dist/src/agent/compaction-agent.js +24 -12
- package/dist/src/agent/context-manager.js +2 -1
- package/dist/src/agent/expand-files.js +2 -1
- package/dist/src/agent/loop.js +71 -33
- package/dist/src/agent/permissions.js +4 -2
- package/dist/src/agent/system-prompt.js +34 -17
- package/dist/src/app.js +1 -1
- package/dist/src/auth/keychain.js +8 -4
- package/dist/src/auth/store.js +70 -107
- package/dist/src/cli/init.js +35 -19
- package/dist/src/cli/run.js +18 -10
- package/dist/src/cli/serve.js +4 -2
- package/dist/src/cli.js +52 -11
- package/dist/src/commands/alias.js +5 -3
- package/dist/src/commands/audit/index.js +2 -1
- package/dist/src/commands/aws-terraform.js +36 -18
- package/dist/src/commands/completions.js +1 -1
- package/dist/src/commands/config.js +3 -2
- package/dist/src/commands/connect-github.js +92 -0
- package/dist/src/commands/cost/index.js +3 -2
- package/dist/src/commands/deploy.js +15 -10
- package/dist/src/commands/doctor.js +9 -6
- package/dist/src/commands/drift/index.js +2 -1
- package/dist/src/commands/export.js +5 -3
- package/dist/src/commands/generate-terraform.js +110 -2
- package/dist/src/commands/import.js +3 -3
- package/dist/src/commands/incident.js +10 -5
- package/dist/src/commands/login.js +8 -93
- package/dist/src/commands/logs.js +16 -8
- package/dist/src/commands/onboarding.js +6 -4
- package/dist/src/commands/pipeline.js +6 -3
- package/dist/src/commands/plugin.js +3 -2
- package/dist/src/commands/profile.js +27 -14
- package/dist/src/commands/questionnaire.js +1 -1
- package/dist/src/commands/rollback.js +3 -2
- package/dist/src/commands/rollout.js +5 -3
- package/dist/src/commands/runbook.js +17 -10
- package/dist/src/commands/schedule.js +10 -5
- package/dist/src/commands/status.js +2 -1
- package/dist/src/commands/team-context.js +12 -7
- package/dist/src/commands/template.js +1 -1
- package/dist/src/commands/tf/index.js +6 -3
- package/dist/src/commands/upgrade.js +5 -3
- package/dist/src/commands/version.js +6 -3
- package/dist/src/commands/watch.js +6 -3
- package/dist/src/compat/sqlite.js +5 -3
- package/dist/src/config/mode-store.js +2 -1
- package/dist/src/config/profiles.js +4 -2
- package/dist/src/config/types.js +2 -1
- package/dist/src/engine/executor.js +8 -4
- package/dist/src/engine/planner.js +9 -5
- package/dist/src/llm/providers/anthropic.js +6 -3
- package/dist/src/llm/providers/ollama.js +1 -1
- package/dist/src/llm/router.js +22 -7
- package/dist/src/nimbus.js +1 -0
- package/dist/src/sessions/manager.js +6 -3
- package/dist/src/sharing/viewer.js +2 -1
- package/dist/src/tools/file-ops.js +1 -2
- package/dist/src/tools/schemas/devops.js +197 -108
- package/dist/src/tools/schemas/standard.js +1 -1
- package/dist/src/ui/App.js +25 -13
- package/dist/src/ui/FileDiffModal.js +22 -11
- package/dist/src/ui/HelpModal.js +2 -1
- package/dist/src/ui/InputBox.js +6 -3
- package/dist/src/ui/MessageList.js +40 -20
- package/dist/src/ui/TerminalPane.js +2 -1
- package/dist/src/ui/ToolCallDisplay.js +12 -6
- package/dist/src/ui/TreePane.js +2 -1
- package/dist/src/ui/ink/index.js +37 -21
- package/dist/src/version.js +1 -1
- package/dist/src/watcher/index.js +8 -4
- package/package.json +3 -5
- package/src/__tests__/alias.test.ts +0 -133
- package/src/__tests__/app.test.ts +0 -76
- package/src/__tests__/audit.test.ts +0 -877
- package/src/__tests__/circuit-breaker.test.ts +0 -116
- package/src/__tests__/cli-run.test.ts +0 -351
- package/src/__tests__/compat-sqlite.test.ts +0 -68
- package/src/__tests__/context-manager.test.ts +0 -632
- package/src/__tests__/context.test.ts +0 -242
- package/src/__tests__/devops-terminal-gaps.test.ts +0 -718
- package/src/__tests__/doctor.test.ts +0 -48
- package/src/__tests__/enterprise.test.ts +0 -401
- package/src/__tests__/export.test.ts +0 -236
- package/src/__tests__/gap-11-18-20.test.ts +0 -958
- package/src/__tests__/generator.test.ts +0 -433
- package/src/__tests__/helm-streaming.test.ts +0 -127
- package/src/__tests__/hooks.test.ts +0 -582
- package/src/__tests__/incident.test.ts +0 -179
- package/src/__tests__/init.test.ts +0 -487
- package/src/__tests__/intent-parser.test.ts +0 -229
- package/src/__tests__/llm-router.test.ts +0 -209
- package/src/__tests__/logs.test.ts +0 -107
- package/src/__tests__/loop-errors.test.ts +0 -244
- package/src/__tests__/lsp.test.ts +0 -293
- package/src/__tests__/modes.test.ts +0 -336
- package/src/__tests__/perf-optimizations.test.ts +0 -847
- package/src/__tests__/permissions.test.ts +0 -338
- package/src/__tests__/pipeline.test.ts +0 -50
- package/src/__tests__/polish-phase3.test.ts +0 -340
- package/src/__tests__/profile.test.ts +0 -237
- package/src/__tests__/rollback.test.ts +0 -83
- package/src/__tests__/runbook.test.ts +0 -219
- package/src/__tests__/schedule.test.ts +0 -206
- package/src/__tests__/serve.test.ts +0 -275
- package/src/__tests__/sessions.test.ts +0 -322
- package/src/__tests__/sharing.test.ts +0 -340
- package/src/__tests__/snapshots.test.ts +0 -581
- package/src/__tests__/standalone-migration.test.ts +0 -199
- package/src/__tests__/state-db.test.ts +0 -334
- package/src/__tests__/status.test.ts +0 -158
- package/src/__tests__/stream-with-tools.test.ts +0 -778
- package/src/__tests__/subagents.test.ts +0 -176
- package/src/__tests__/system-prompt.test.ts +0 -248
- package/src/__tests__/terminal-gap-v2.test.ts +0 -395
- package/src/__tests__/terminal-parity.test.ts +0 -393
- package/src/__tests__/tf-apply.test.ts +0 -187
- package/src/__tests__/tool-converter.test.ts +0 -256
- package/src/__tests__/tool-schemas.test.ts +0 -602
- package/src/__tests__/tools.test.ts +0 -144
- package/src/__tests__/version-json.test.ts +0 -184
- package/src/__tests__/version.test.ts +0 -49
- package/src/__tests__/watch.test.ts +0 -129
- package/src/agent/compaction-agent.ts +0 -266
- package/src/agent/context-manager.ts +0 -499
- package/src/agent/context.ts +0 -427
- package/src/agent/deploy-preview.ts +0 -487
- package/src/agent/expand-files.ts +0 -108
- package/src/agent/index.ts +0 -68
- package/src/agent/loop.ts +0 -1998
- package/src/agent/modes.ts +0 -429
- package/src/agent/permissions.ts +0 -513
- package/src/agent/subagents/base.ts +0 -116
- package/src/agent/subagents/cost.ts +0 -51
- package/src/agent/subagents/explore.ts +0 -42
- package/src/agent/subagents/general.ts +0 -54
- package/src/agent/subagents/index.ts +0 -102
- package/src/agent/subagents/infra.ts +0 -59
- package/src/agent/subagents/security.ts +0 -69
- package/src/agent/system-prompt.ts +0 -990
- package/src/app.ts +0 -180
- package/src/audit/activity-log.ts +0 -290
- package/src/audit/compliance-checker.ts +0 -540
- package/src/audit/cost-tracker.ts +0 -318
- package/src/audit/index.ts +0 -23
- package/src/audit/security-scanner.ts +0 -641
- package/src/auth/guard.ts +0 -75
- package/src/auth/index.ts +0 -56
- package/src/auth/keychain.ts +0 -82
- package/src/auth/oauth.ts +0 -465
- package/src/auth/providers.ts +0 -470
- package/src/auth/sso.ts +0 -113
- package/src/auth/store.ts +0 -505
- package/src/auth/types.ts +0 -187
- package/src/build.ts +0 -141
- package/src/cli/index.ts +0 -16
- package/src/cli/init.ts +0 -1227
- package/src/cli/openapi-spec.ts +0 -356
- package/src/cli/run.ts +0 -628
- package/src/cli/serve-auth.ts +0 -80
- package/src/cli/serve.ts +0 -539
- package/src/cli/web.ts +0 -71
- package/src/cli.ts +0 -1728
- package/src/clients/core-engine-client.ts +0 -227
- package/src/clients/enterprise-client.ts +0 -334
- package/src/clients/generator-client.ts +0 -351
- package/src/clients/git-client.ts +0 -627
- package/src/clients/github-client.ts +0 -410
- package/src/clients/helm-client.ts +0 -504
- package/src/clients/index.ts +0 -80
- package/src/clients/k8s-client.ts +0 -497
- package/src/clients/llm-client.ts +0 -161
- package/src/clients/rest-client.ts +0 -130
- package/src/clients/service-discovery.ts +0 -38
- package/src/clients/terraform-client.ts +0 -482
- package/src/clients/tools-client.ts +0 -1843
- package/src/clients/ws-client.ts +0 -115
- package/src/commands/alias.ts +0 -100
- package/src/commands/analyze/index.ts +0 -352
- package/src/commands/apply/helm.ts +0 -473
- package/src/commands/apply/index.ts +0 -213
- package/src/commands/apply/k8s.ts +0 -454
- package/src/commands/apply/terraform.ts +0 -582
- package/src/commands/ask.ts +0 -167
- package/src/commands/audit/index.ts +0 -357
- package/src/commands/auth-cloud.ts +0 -407
- package/src/commands/auth-list.ts +0 -134
- package/src/commands/auth-profile.ts +0 -121
- package/src/commands/auth-refresh.ts +0 -187
- package/src/commands/auth-status.ts +0 -141
- package/src/commands/aws/ec2.ts +0 -501
- package/src/commands/aws/iam.ts +0 -397
- package/src/commands/aws/index.ts +0 -133
- package/src/commands/aws/lambda.ts +0 -396
- package/src/commands/aws/rds.ts +0 -439
- package/src/commands/aws/s3.ts +0 -439
- package/src/commands/aws/vpc.ts +0 -393
- package/src/commands/aws-discover.ts +0 -542
- package/src/commands/aws-terraform.ts +0 -755
- package/src/commands/azure/aks.ts +0 -376
- package/src/commands/azure/functions.ts +0 -253
- package/src/commands/azure/index.ts +0 -116
- package/src/commands/azure/storage.ts +0 -478
- package/src/commands/azure/vm.ts +0 -355
- package/src/commands/billing/index.ts +0 -256
- package/src/commands/chat.ts +0 -320
- package/src/commands/completions.ts +0 -268
- package/src/commands/config.ts +0 -372
- package/src/commands/cost/cloud-cost-estimator.ts +0 -266
- package/src/commands/cost/estimator.ts +0 -79
- package/src/commands/cost/index.ts +0 -810
- package/src/commands/cost/parsers/terraform.ts +0 -273
- package/src/commands/cost/parsers/types.ts +0 -25
- package/src/commands/cost/pricing/aws.ts +0 -544
- package/src/commands/cost/pricing/azure.ts +0 -499
- package/src/commands/cost/pricing/gcp.ts +0 -396
- package/src/commands/cost/pricing/index.ts +0 -40
- package/src/commands/demo.ts +0 -250
- package/src/commands/deploy.ts +0 -260
- package/src/commands/doctor.ts +0 -1386
- package/src/commands/drift/index.ts +0 -787
- package/src/commands/explain.ts +0 -277
- package/src/commands/export.ts +0 -146
- package/src/commands/feedback.ts +0 -389
- package/src/commands/fix.ts +0 -324
- package/src/commands/fs/index.ts +0 -402
- package/src/commands/gcp/compute.ts +0 -325
- package/src/commands/gcp/functions.ts +0 -271
- package/src/commands/gcp/gke.ts +0 -438
- package/src/commands/gcp/iam.ts +0 -344
- package/src/commands/gcp/index.ts +0 -129
- package/src/commands/gcp/storage.ts +0 -284
- package/src/commands/generate-helm.ts +0 -1249
- package/src/commands/generate-k8s.ts +0 -1508
- package/src/commands/generate-terraform.ts +0 -1202
- package/src/commands/gh/index.ts +0 -863
- package/src/commands/git/index.ts +0 -1343
- package/src/commands/helm/index.ts +0 -1126
- package/src/commands/help.ts +0 -715
- package/src/commands/history.ts +0 -149
- package/src/commands/import.ts +0 -868
- package/src/commands/incident.ts +0 -166
- package/src/commands/index.ts +0 -367
- package/src/commands/init.ts +0 -1051
- package/src/commands/k8s/index.ts +0 -1137
- package/src/commands/login.ts +0 -716
- package/src/commands/logout.ts +0 -83
- package/src/commands/logs.ts +0 -167
- package/src/commands/onboarding.ts +0 -405
- package/src/commands/pipeline.ts +0 -186
- package/src/commands/plan/display.ts +0 -279
- package/src/commands/plan/index.ts +0 -599
- package/src/commands/plugin.ts +0 -398
- package/src/commands/preview.ts +0 -452
- package/src/commands/profile.ts +0 -342
- package/src/commands/questionnaire.ts +0 -1172
- package/src/commands/resume.ts +0 -47
- package/src/commands/rollback.ts +0 -315
- package/src/commands/rollout.ts +0 -88
- package/src/commands/runbook.ts +0 -346
- package/src/commands/schedule.ts +0 -236
- package/src/commands/status.ts +0 -252
- package/src/commands/team/index.ts +0 -346
- package/src/commands/team-context.ts +0 -220
- package/src/commands/template.ts +0 -233
- package/src/commands/tf/index.ts +0 -1093
- package/src/commands/upgrade.ts +0 -607
- package/src/commands/usage/index.ts +0 -134
- package/src/commands/version.ts +0 -174
- package/src/commands/watch.ts +0 -153
- package/src/compat/index.ts +0 -2
- package/src/compat/runtime.ts +0 -12
- package/src/compat/sqlite.ts +0 -177
- package/src/config/index.ts +0 -17
- package/src/config/manager.ts +0 -530
- package/src/config/mode-store.ts +0 -62
- package/src/config/profiles.ts +0 -84
- package/src/config/safety-policy.ts +0 -358
- package/src/config/schema.ts +0 -125
- package/src/config/types.ts +0 -609
- package/src/config/workspace-state.ts +0 -53
- package/src/context/context-db.ts +0 -199
- package/src/demo/index.ts +0 -349
- package/src/demo/scenarios/full-journey.ts +0 -229
- package/src/demo/scenarios/getting-started.ts +0 -127
- package/src/demo/scenarios/helm-release.ts +0 -341
- package/src/demo/scenarios/k8s-deployment.ts +0 -194
- package/src/demo/scenarios/terraform-vpc.ts +0 -170
- package/src/demo/types.ts +0 -92
- package/src/engine/cost-estimator.ts +0 -480
- package/src/engine/diagram-generator.ts +0 -256
- package/src/engine/drift-detector.ts +0 -902
- package/src/engine/executor.ts +0 -1066
- package/src/engine/index.ts +0 -76
- package/src/engine/orchestrator.ts +0 -636
- package/src/engine/planner.ts +0 -787
- package/src/engine/safety.ts +0 -743
- package/src/engine/verifier.ts +0 -770
- package/src/enterprise/audit.ts +0 -348
- package/src/enterprise/auth.ts +0 -270
- package/src/enterprise/billing.ts +0 -822
- package/src/enterprise/index.ts +0 -17
- package/src/enterprise/teams.ts +0 -443
- package/src/generator/best-practices.ts +0 -1608
- package/src/generator/helm.ts +0 -630
- package/src/generator/index.ts +0 -37
- package/src/generator/intent-parser.ts +0 -514
- package/src/generator/kubernetes.ts +0 -976
- package/src/generator/terraform.ts +0 -1875
- package/src/history/index.ts +0 -8
- package/src/history/manager.ts +0 -250
- package/src/history/types.ts +0 -34
- package/src/hooks/config.ts +0 -432
- package/src/hooks/engine.ts +0 -392
- package/src/hooks/index.ts +0 -4
- package/src/llm/auth-bridge.ts +0 -198
- package/src/llm/circuit-breaker.ts +0 -140
- package/src/llm/config-loader.ts +0 -201
- package/src/llm/cost-calculator.ts +0 -171
- package/src/llm/index.ts +0 -8
- package/src/llm/model-aliases.ts +0 -115
- package/src/llm/provider-registry.ts +0 -63
- package/src/llm/providers/anthropic.ts +0 -462
- package/src/llm/providers/bedrock.ts +0 -477
- package/src/llm/providers/google.ts +0 -405
- package/src/llm/providers/ollama.ts +0 -767
- package/src/llm/providers/openai-compatible.ts +0 -340
- package/src/llm/providers/openai.ts +0 -328
- package/src/llm/providers/openrouter.ts +0 -338
- package/src/llm/router.ts +0 -1104
- package/src/llm/types.ts +0 -232
- package/src/lsp/client.ts +0 -298
- package/src/lsp/languages.ts +0 -119
- package/src/lsp/manager.ts +0 -294
- package/src/mcp/client.ts +0 -402
- package/src/mcp/index.ts +0 -5
- package/src/mcp/manager.ts +0 -133
- package/src/nimbus.ts +0 -233
- package/src/plugins/index.ts +0 -27
- package/src/plugins/loader.ts +0 -334
- package/src/plugins/manager.ts +0 -376
- package/src/plugins/types.ts +0 -284
- package/src/scanners/cicd-scanner.ts +0 -258
- package/src/scanners/cloud-scanner.ts +0 -466
- package/src/scanners/framework-scanner.ts +0 -469
- package/src/scanners/iac-scanner.ts +0 -388
- package/src/scanners/index.ts +0 -539
- package/src/scanners/language-scanner.ts +0 -276
- package/src/scanners/package-manager-scanner.ts +0 -277
- package/src/scanners/types.ts +0 -172
- package/src/sessions/manager.ts +0 -472
- package/src/sessions/types.ts +0 -44
- package/src/sharing/sync.ts +0 -300
- package/src/sharing/viewer.ts +0 -163
- package/src/snapshots/index.ts +0 -2
- package/src/snapshots/manager.ts +0 -530
- package/src/state/artifacts.ts +0 -147
- package/src/state/audit.ts +0 -137
- package/src/state/billing.ts +0 -240
- package/src/state/checkpoints.ts +0 -117
- package/src/state/config.ts +0 -67
- package/src/state/conversations.ts +0 -14
- package/src/state/credentials.ts +0 -154
- package/src/state/db.ts +0 -58
- package/src/state/index.ts +0 -26
- package/src/state/messages.ts +0 -115
- package/src/state/projects.ts +0 -123
- package/src/state/schema.ts +0 -236
- package/src/state/sessions.ts +0 -147
- package/src/state/teams.ts +0 -200
- package/src/telemetry.ts +0 -108
- package/src/tools/aws-ops.ts +0 -952
- package/src/tools/azure-ops.ts +0 -579
- package/src/tools/file-ops.ts +0 -615
- package/src/tools/gcp-ops.ts +0 -625
- package/src/tools/git-ops.ts +0 -773
- package/src/tools/github-ops.ts +0 -799
- package/src/tools/helm-ops.ts +0 -943
- package/src/tools/index.ts +0 -17
- package/src/tools/k8s-ops.ts +0 -819
- package/src/tools/schemas/converter.ts +0 -184
- package/src/tools/schemas/devops.ts +0 -3502
- package/src/tools/schemas/index.ts +0 -73
- package/src/tools/schemas/standard.ts +0 -1148
- package/src/tools/schemas/types.ts +0 -735
- package/src/tools/spawn-exec.ts +0 -148
- package/src/tools/terraform-ops.ts +0 -862
- package/src/types/ambient.d.ts +0 -193
- package/src/types/config.ts +0 -83
- package/src/types/drift.ts +0 -116
- package/src/types/enterprise.ts +0 -335
- package/src/types/index.ts +0 -20
- package/src/types/plan.ts +0 -44
- package/src/types/request.ts +0 -65
- package/src/types/response.ts +0 -54
- package/src/types/service.ts +0 -51
- package/src/ui/App.tsx +0 -2114
- package/src/ui/DeployPreview.tsx +0 -174
- package/src/ui/FileDiffModal.tsx +0 -162
- package/src/ui/Header.tsx +0 -131
- package/src/ui/HelpModal.tsx +0 -57
- package/src/ui/InputBox.tsx +0 -503
- package/src/ui/MessageList.tsx +0 -1032
- package/src/ui/PermissionPrompt.tsx +0 -163
- package/src/ui/StatusBar.tsx +0 -277
- package/src/ui/TerminalPane.tsx +0 -84
- package/src/ui/ToolCallDisplay.tsx +0 -643
- package/src/ui/TreePane.tsx +0 -132
- package/src/ui/chat-ui.ts +0 -850
- package/src/ui/index.ts +0 -33
- package/src/ui/ink/index.ts +0 -1444
- package/src/ui/streaming.ts +0 -176
- package/src/ui/theme.ts +0 -104
- package/src/ui/types.ts +0 -75
- package/src/utils/analytics.ts +0 -72
- package/src/utils/cost-warning.ts +0 -27
- package/src/utils/env.ts +0 -46
- package/src/utils/errors.ts +0 -69
- package/src/utils/event-bus.ts +0 -38
- package/src/utils/index.ts +0 -24
- package/src/utils/logger.ts +0 -171
- package/src/utils/rate-limiter.ts +0 -121
- package/src/utils/service-auth.ts +0 -49
- package/src/utils/validation.ts +0 -53
- package/src/version.ts +0 -4
- package/src/watcher/index.ts +0 -214
- package/src/wizard/approval.ts +0 -383
- package/src/wizard/index.ts +0 -25
- package/src/wizard/prompts.ts +0 -338
- package/src/wizard/types.ts +0 -172
- package/src/wizard/ui.ts +0 -556
- package/src/wizard/wizard.ts +0 -304
- package/tsconfig.json +0 -24
|
@@ -1,1875 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Terraform Project Generator
|
|
3
|
-
*
|
|
4
|
-
* Generates complete Terraform project structures with environment separation,
|
|
5
|
-
* module scaffolding, and post-generation validation pipeline.
|
|
6
|
-
*
|
|
7
|
-
* Addresses:
|
|
8
|
-
* - Gap #9: Post-generation validation pipeline
|
|
9
|
-
* - Gap #10: Environment separation (dev/staging/prod tfvars)
|
|
10
|
-
* - Gap #11: Full project structure generation
|
|
11
|
-
* - Gap #12: tflint-style checks
|
|
12
|
-
* - Gap #16: .gitignore in scaffolded projects
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { spawn } from 'node:child_process';
|
|
16
|
-
import { logger } from '../utils';
|
|
17
|
-
|
|
18
|
-
// ==========================================
|
|
19
|
-
// Types
|
|
20
|
-
// ==========================================
|
|
21
|
-
|
|
22
|
-
/** Configuration for generating a Terraform project. */
|
|
23
|
-
export interface TerraformProjectConfig {
|
|
24
|
-
/** Name of the project used for resource naming and tagging. */
|
|
25
|
-
projectName: string;
|
|
26
|
-
/** Cloud provider target. */
|
|
27
|
-
provider: 'aws' | 'gcp' | 'azure';
|
|
28
|
-
/** Cloud provider region. */
|
|
29
|
-
region: string;
|
|
30
|
-
/** Default environment. */
|
|
31
|
-
environment?: string;
|
|
32
|
-
/** Infrastructure components to include (e.g. vpc, eks, rds, s3, ecs, kms). */
|
|
33
|
-
components: string[];
|
|
34
|
-
/** Remote state backend configuration. */
|
|
35
|
-
backendConfig?: {
|
|
36
|
-
bucket: string;
|
|
37
|
-
dynamodbTable?: string;
|
|
38
|
-
key?: string;
|
|
39
|
-
};
|
|
40
|
-
/** Common resource tags. */
|
|
41
|
-
tags?: Record<string, string>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** A single generated file with its relative path and content. */
|
|
45
|
-
export interface GeneratedFile {
|
|
46
|
-
path: string;
|
|
47
|
-
content: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** A single item from the validation pipeline. */
|
|
51
|
-
export interface ValidationItem {
|
|
52
|
-
severity: 'error' | 'warning' | 'info';
|
|
53
|
-
message: string;
|
|
54
|
-
file?: string;
|
|
55
|
-
line?: number;
|
|
56
|
-
rule?: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Aggregated validation report for a generated project. */
|
|
60
|
-
export interface ValidationReport {
|
|
61
|
-
valid: boolean;
|
|
62
|
-
items: ValidationItem[];
|
|
63
|
-
summary: {
|
|
64
|
-
errors: number;
|
|
65
|
-
warnings: number;
|
|
66
|
-
info: number;
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Result from running a subprocess command (terraform fmt, validate, tflint). */
|
|
71
|
-
export interface SubprocessResult {
|
|
72
|
-
/** Whether the command exited with code 0. */
|
|
73
|
-
success: boolean;
|
|
74
|
-
/** Standard output from the command. */
|
|
75
|
-
stdout: string;
|
|
76
|
-
/** Standard error from the command. */
|
|
77
|
-
stderr: string;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Aggregated results from all subprocess validation steps. */
|
|
81
|
-
export interface SubprocessValidation {
|
|
82
|
-
/** Result of `terraform fmt -check -diff`. */
|
|
83
|
-
fmtCheck: SubprocessResult;
|
|
84
|
-
/** Result of `terraform init -backend=false` followed by `terraform validate`. */
|
|
85
|
-
terraformValidate: SubprocessResult;
|
|
86
|
-
/** Result of `tflint` if installed, null otherwise. */
|
|
87
|
-
tflint: SubprocessResult | null;
|
|
88
|
-
/** Result of `checkov` if installed, null otherwise. */
|
|
89
|
-
checkov: SubprocessResult | null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** The complete output of the project generator. */
|
|
93
|
-
export interface GeneratedProject {
|
|
94
|
-
files: GeneratedFile[];
|
|
95
|
-
validation: ValidationReport;
|
|
96
|
-
/** Subprocess-based validation results (best-effort; omitted when terraform CLI is unavailable). */
|
|
97
|
-
subprocessValidation?: SubprocessValidation;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ==========================================
|
|
101
|
-
// Generator
|
|
102
|
-
// ==========================================
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Generates a complete Terraform project structure including:
|
|
106
|
-
* - Root configuration files (main.tf, variables.tf, outputs.tf, versions.tf, backend.tf)
|
|
107
|
-
* - Environment-specific tfvars (dev, staging, prod)
|
|
108
|
-
* - Component modules with main, variables, and outputs
|
|
109
|
-
* - .gitignore for Terraform projects
|
|
110
|
-
* - Post-generation validation pipeline
|
|
111
|
-
*/
|
|
112
|
-
export class TerraformProjectGenerator {
|
|
113
|
-
/**
|
|
114
|
-
* Generate a full Terraform project from the given configuration.
|
|
115
|
-
*/
|
|
116
|
-
async generate(config: TerraformProjectConfig): Promise<GeneratedProject> {
|
|
117
|
-
logger.info(`Generating Terraform project: ${config.projectName}`);
|
|
118
|
-
|
|
119
|
-
const files: GeneratedFile[] = [];
|
|
120
|
-
|
|
121
|
-
// 1. Root configuration files
|
|
122
|
-
files.push(this.generateMainTf(config));
|
|
123
|
-
files.push(this.generateVariablesTf(config));
|
|
124
|
-
files.push(this.generateOutputsTf(config));
|
|
125
|
-
files.push(this.generateVersionsTf(config));
|
|
126
|
-
files.push(this.generateBackendTf(config));
|
|
127
|
-
|
|
128
|
-
// 2. Example tfvars
|
|
129
|
-
files.push(this.generateTfvarsExample(config));
|
|
130
|
-
|
|
131
|
-
// 3. README
|
|
132
|
-
files.push(this.generateReadme(config));
|
|
133
|
-
|
|
134
|
-
// 4. Environment-specific tfvars (Gap #10)
|
|
135
|
-
files.push(this.generateEnvTfvars(config, 'dev'));
|
|
136
|
-
files.push(this.generateEnvTfvars(config, 'staging'));
|
|
137
|
-
files.push(this.generateEnvTfvars(config, 'prod'));
|
|
138
|
-
|
|
139
|
-
// 5. Module files for each component (Gap #11)
|
|
140
|
-
for (const component of config.components) {
|
|
141
|
-
files.push(...this.generateModuleFiles(config, component));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// 6. .gitignore for Terraform projects (Gap #16)
|
|
145
|
-
files.push(this.generateGitignore());
|
|
146
|
-
|
|
147
|
-
// 7. Run validation pipeline (Gap #9 + #12)
|
|
148
|
-
const validation = this.validateProject(files, config);
|
|
149
|
-
|
|
150
|
-
// Subprocess validation (D1) is available via validateWithSubprocess()
|
|
151
|
-
// but is NOT auto-run here because terraform init can be slow (downloads providers).
|
|
152
|
-
// Callers should invoke validateWithSubprocess() separately when needed.
|
|
153
|
-
return { files, validation };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ===== File Generators =====
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Generate a standard Terraform .gitignore file.
|
|
160
|
-
* Excludes state files, provider caches, variable overrides,
|
|
161
|
-
* and other files that should not be committed to version control.
|
|
162
|
-
*/
|
|
163
|
-
generateGitignore(): GeneratedFile {
|
|
164
|
-
return {
|
|
165
|
-
path: '.gitignore',
|
|
166
|
-
content: `# Terraform
|
|
167
|
-
*.tfstate
|
|
168
|
-
*.tfstate.*
|
|
169
|
-
.terraform/
|
|
170
|
-
.terraform.lock.hcl
|
|
171
|
-
crash.log
|
|
172
|
-
override.tf
|
|
173
|
-
override.tf.json
|
|
174
|
-
*_override.tf
|
|
175
|
-
*_override.tf.json
|
|
176
|
-
*.tfvars
|
|
177
|
-
*.tfvars.json
|
|
178
|
-
.terraformrc
|
|
179
|
-
terraform.rc
|
|
180
|
-
`,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
private generateMainTf(config: TerraformProjectConfig): GeneratedFile {
|
|
185
|
-
const providerBlock = this.getProviderBlock(config);
|
|
186
|
-
const moduleBlocks = config.components.map(c => this.getModuleBlock(config, c)).join('\n\n');
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
path: 'main.tf',
|
|
190
|
-
content: `# ${config.projectName} - Main Configuration
|
|
191
|
-
# Generated by Nimbus
|
|
192
|
-
|
|
193
|
-
${providerBlock}
|
|
194
|
-
|
|
195
|
-
${moduleBlocks}
|
|
196
|
-
`,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private generateVariablesTf(config: TerraformProjectConfig): GeneratedFile {
|
|
201
|
-
const vars: string[] = [
|
|
202
|
-
`# ${config.projectName} - Variables`,
|
|
203
|
-
'# Generated by Nimbus',
|
|
204
|
-
'',
|
|
205
|
-
'variable "project_name" {',
|
|
206
|
-
' description = "Name of the project"',
|
|
207
|
-
' type = string',
|
|
208
|
-
` default = "${config.projectName}"`,
|
|
209
|
-
'}',
|
|
210
|
-
'',
|
|
211
|
-
'variable "environment" {',
|
|
212
|
-
' description = "Environment (dev, staging, prod)"',
|
|
213
|
-
' type = string',
|
|
214
|
-
` default = "${config.environment || 'dev'}"`,
|
|
215
|
-
'',
|
|
216
|
-
' validation {',
|
|
217
|
-
' condition = contains(["dev", "staging", "prod"], var.environment)',
|
|
218
|
-
' error_message = "Environment must be dev, staging, or prod."',
|
|
219
|
-
' }',
|
|
220
|
-
'}',
|
|
221
|
-
'',
|
|
222
|
-
'variable "region" {',
|
|
223
|
-
' description = "Cloud provider region"',
|
|
224
|
-
' type = string',
|
|
225
|
-
` default = "${config.region}"`,
|
|
226
|
-
'}',
|
|
227
|
-
'',
|
|
228
|
-
'variable "tags" {',
|
|
229
|
-
' description = "Common tags for all resources"',
|
|
230
|
-
' type = map(string)',
|
|
231
|
-
' default = {',
|
|
232
|
-
` Project = "${config.projectName}"`,
|
|
233
|
-
' ManagedBy = "terraform"',
|
|
234
|
-
' Environment = "dev"',
|
|
235
|
-
' }',
|
|
236
|
-
'}',
|
|
237
|
-
];
|
|
238
|
-
|
|
239
|
-
// Add component-specific variables
|
|
240
|
-
if (config.components.includes('vpc')) {
|
|
241
|
-
vars.push(
|
|
242
|
-
'',
|
|
243
|
-
'variable "vpc_cidr" {',
|
|
244
|
-
' description = "VPC CIDR block"',
|
|
245
|
-
' type = string',
|
|
246
|
-
' default = "10.0.0.0/16"',
|
|
247
|
-
'}'
|
|
248
|
-
);
|
|
249
|
-
vars.push(
|
|
250
|
-
'',
|
|
251
|
-
'variable "availability_zones" {',
|
|
252
|
-
' description = "List of availability zones"',
|
|
253
|
-
' type = list(string)',
|
|
254
|
-
` default = ["${config.region}a", "${config.region}b"]`,
|
|
255
|
-
'}'
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (config.components.includes('eks')) {
|
|
260
|
-
vars.push(
|
|
261
|
-
'',
|
|
262
|
-
'variable "cluster_version" {',
|
|
263
|
-
' description = "EKS cluster version"',
|
|
264
|
-
' type = string',
|
|
265
|
-
' default = "1.28"',
|
|
266
|
-
'}'
|
|
267
|
-
);
|
|
268
|
-
vars.push(
|
|
269
|
-
'',
|
|
270
|
-
'variable "node_instance_type" {',
|
|
271
|
-
' description = "EKS node instance type"',
|
|
272
|
-
' type = string',
|
|
273
|
-
' default = "t3.medium"',
|
|
274
|
-
'}'
|
|
275
|
-
);
|
|
276
|
-
vars.push(
|
|
277
|
-
'',
|
|
278
|
-
'variable "node_count" {',
|
|
279
|
-
' description = "Number of EKS worker nodes"',
|
|
280
|
-
' type = number',
|
|
281
|
-
' default = 2',
|
|
282
|
-
'}'
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (config.components.includes('rds')) {
|
|
287
|
-
vars.push(
|
|
288
|
-
'',
|
|
289
|
-
'variable "db_instance_class" {',
|
|
290
|
-
' description = "RDS instance class"',
|
|
291
|
-
' type = string',
|
|
292
|
-
' default = "db.t3.micro"',
|
|
293
|
-
'}'
|
|
294
|
-
);
|
|
295
|
-
vars.push(
|
|
296
|
-
'',
|
|
297
|
-
'variable "db_engine" {',
|
|
298
|
-
' description = "Database engine"',
|
|
299
|
-
' type = string',
|
|
300
|
-
' default = "postgres"',
|
|
301
|
-
'}'
|
|
302
|
-
);
|
|
303
|
-
vars.push(
|
|
304
|
-
'',
|
|
305
|
-
'variable "db_storage_size" {',
|
|
306
|
-
' description = "Database storage size in GB"',
|
|
307
|
-
' type = number',
|
|
308
|
-
' default = 20',
|
|
309
|
-
'}'
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (config.components.includes('s3')) {
|
|
314
|
-
vars.push(
|
|
315
|
-
'',
|
|
316
|
-
'variable "bucket_name" {',
|
|
317
|
-
' description = "S3 bucket name"',
|
|
318
|
-
' type = string',
|
|
319
|
-
` default = "${config.projectName}-storage"`,
|
|
320
|
-
'}'
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (config.components.includes('ecs')) {
|
|
325
|
-
vars.push(
|
|
326
|
-
'',
|
|
327
|
-
'variable "container_image" {',
|
|
328
|
-
' description = "Docker image for the ECS task"',
|
|
329
|
-
' type = string',
|
|
330
|
-
` default = "${config.projectName}:latest"`,
|
|
331
|
-
'}'
|
|
332
|
-
);
|
|
333
|
-
vars.push(
|
|
334
|
-
'',
|
|
335
|
-
'variable "container_port" {',
|
|
336
|
-
' description = "Port exposed by the container"',
|
|
337
|
-
' type = number',
|
|
338
|
-
' default = 8080',
|
|
339
|
-
'}'
|
|
340
|
-
);
|
|
341
|
-
vars.push(
|
|
342
|
-
'',
|
|
343
|
-
'variable "ecs_cpu" {',
|
|
344
|
-
' description = "Fargate task CPU units (256, 512, 1024, 2048, 4096)"',
|
|
345
|
-
' type = number',
|
|
346
|
-
' default = 256',
|
|
347
|
-
'}'
|
|
348
|
-
);
|
|
349
|
-
vars.push(
|
|
350
|
-
'',
|
|
351
|
-
'variable "ecs_memory" {',
|
|
352
|
-
' description = "Fargate task memory in MiB"',
|
|
353
|
-
' type = number',
|
|
354
|
-
' default = 512',
|
|
355
|
-
'}'
|
|
356
|
-
);
|
|
357
|
-
vars.push(
|
|
358
|
-
'',
|
|
359
|
-
'variable "desired_count" {',
|
|
360
|
-
' description = "Number of ECS tasks to run"',
|
|
361
|
-
' type = number',
|
|
362
|
-
' default = 2',
|
|
363
|
-
'}'
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (config.components.includes('kms')) {
|
|
368
|
-
vars.push(
|
|
369
|
-
'',
|
|
370
|
-
'variable "kms_key_alias" {',
|
|
371
|
-
' description = "Alias for the KMS key"',
|
|
372
|
-
' type = string',
|
|
373
|
-
` default = "${config.projectName}-key"`,
|
|
374
|
-
'}'
|
|
375
|
-
);
|
|
376
|
-
vars.push(
|
|
377
|
-
'',
|
|
378
|
-
'variable "kms_deletion_window" {',
|
|
379
|
-
' description = "Number of days before KMS key is deleted after destruction"',
|
|
380
|
-
' type = number',
|
|
381
|
-
' default = 30',
|
|
382
|
-
'}'
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return { path: 'variables.tf', content: `${vars.join('\n')}\n` };
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
private generateOutputsTf(config: TerraformProjectConfig): GeneratedFile {
|
|
390
|
-
const outputs: string[] = [`# ${config.projectName} - Outputs`, '# Generated by Nimbus', ''];
|
|
391
|
-
|
|
392
|
-
if (config.components.includes('vpc')) {
|
|
393
|
-
outputs.push(
|
|
394
|
-
'output "vpc_id" {',
|
|
395
|
-
' description = "VPC ID"',
|
|
396
|
-
' value = module.vpc.vpc_id',
|
|
397
|
-
'}',
|
|
398
|
-
''
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (config.components.includes('eks')) {
|
|
403
|
-
outputs.push(
|
|
404
|
-
'output "eks_cluster_endpoint" {',
|
|
405
|
-
' description = "EKS cluster endpoint"',
|
|
406
|
-
' value = module.eks.cluster_endpoint',
|
|
407
|
-
'}',
|
|
408
|
-
''
|
|
409
|
-
);
|
|
410
|
-
outputs.push(
|
|
411
|
-
'output "eks_cluster_name" {',
|
|
412
|
-
' description = "EKS cluster name"',
|
|
413
|
-
' value = module.eks.cluster_name',
|
|
414
|
-
'}',
|
|
415
|
-
''
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (config.components.includes('rds')) {
|
|
420
|
-
outputs.push(
|
|
421
|
-
'output "rds_endpoint" {',
|
|
422
|
-
' description = "RDS endpoint"',
|
|
423
|
-
' value = module.rds.endpoint',
|
|
424
|
-
' sensitive = true',
|
|
425
|
-
'}',
|
|
426
|
-
''
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (config.components.includes('s3')) {
|
|
431
|
-
outputs.push(
|
|
432
|
-
'output "s3_bucket_arn" {',
|
|
433
|
-
' description = "S3 bucket ARN"',
|
|
434
|
-
' value = module.s3.bucket_arn',
|
|
435
|
-
'}',
|
|
436
|
-
''
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (config.components.includes('ecs')) {
|
|
441
|
-
outputs.push(
|
|
442
|
-
'output "ecs_cluster_name" {',
|
|
443
|
-
' description = "ECS cluster name"',
|
|
444
|
-
' value = module.ecs.cluster_name',
|
|
445
|
-
'}',
|
|
446
|
-
''
|
|
447
|
-
);
|
|
448
|
-
outputs.push(
|
|
449
|
-
'output "ecs_service_name" {',
|
|
450
|
-
' description = "ECS service name"',
|
|
451
|
-
' value = module.ecs.service_name',
|
|
452
|
-
'}',
|
|
453
|
-
''
|
|
454
|
-
);
|
|
455
|
-
outputs.push(
|
|
456
|
-
'output "alb_dns_name" {',
|
|
457
|
-
' description = "ALB DNS name"',
|
|
458
|
-
' value = module.ecs.alb_dns_name',
|
|
459
|
-
'}',
|
|
460
|
-
''
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (config.components.includes('kms')) {
|
|
465
|
-
outputs.push(
|
|
466
|
-
'output "kms_key_arn" {',
|
|
467
|
-
' description = "KMS key ARN"',
|
|
468
|
-
' value = module.kms.key_arn',
|
|
469
|
-
'}',
|
|
470
|
-
''
|
|
471
|
-
);
|
|
472
|
-
outputs.push(
|
|
473
|
-
'output "kms_key_id" {',
|
|
474
|
-
' description = "KMS key ID"',
|
|
475
|
-
' value = module.kms.key_id',
|
|
476
|
-
'}',
|
|
477
|
-
''
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return { path: 'outputs.tf', content: outputs.join('\n') };
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
private generateVersionsTf(config: TerraformProjectConfig): GeneratedFile {
|
|
485
|
-
const providerSource =
|
|
486
|
-
config.provider === 'aws'
|
|
487
|
-
? 'hashicorp/aws'
|
|
488
|
-
: config.provider === 'gcp'
|
|
489
|
-
? 'hashicorp/google'
|
|
490
|
-
: 'hashicorp/azurerm';
|
|
491
|
-
|
|
492
|
-
const providerVersion =
|
|
493
|
-
config.provider === 'aws' ? '~> 5.0' : config.provider === 'gcp' ? '~> 5.0' : '~> 3.0';
|
|
494
|
-
|
|
495
|
-
const providerName = config.provider === 'gcp' ? 'google' : config.provider;
|
|
496
|
-
|
|
497
|
-
return {
|
|
498
|
-
path: 'versions.tf',
|
|
499
|
-
content: `# Terraform and Provider Versions
|
|
500
|
-
# Generated by Nimbus
|
|
501
|
-
|
|
502
|
-
terraform {
|
|
503
|
-
required_version = ">= 1.5.0"
|
|
504
|
-
|
|
505
|
-
required_providers {
|
|
506
|
-
${providerName} = {
|
|
507
|
-
source = "${providerSource}"
|
|
508
|
-
version = "${providerVersion}"
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
`,
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
private generateBackendTf(config: TerraformProjectConfig): GeneratedFile {
|
|
517
|
-
const bucket = config.backendConfig?.bucket || `${config.projectName}-tfstate`;
|
|
518
|
-
const key = config.backendConfig?.key || `${config.projectName}/terraform.tfstate`;
|
|
519
|
-
const dynamodbTable = config.backendConfig?.dynamodbTable || `${config.projectName}-tflock`;
|
|
520
|
-
|
|
521
|
-
if (config.provider === 'aws') {
|
|
522
|
-
return {
|
|
523
|
-
path: 'backend.tf',
|
|
524
|
-
content: `# Remote State Configuration
|
|
525
|
-
# Generated by Nimbus
|
|
526
|
-
|
|
527
|
-
terraform {
|
|
528
|
-
backend "s3" {
|
|
529
|
-
bucket = "${bucket}"
|
|
530
|
-
key = "${key}"
|
|
531
|
-
region = "${config.region}"
|
|
532
|
-
dynamodb_table = "${dynamodbTable}"
|
|
533
|
-
encrypt = true
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
`,
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// GCP/Azure backends
|
|
541
|
-
return {
|
|
542
|
-
path: 'backend.tf',
|
|
543
|
-
content: `# Remote State Configuration
|
|
544
|
-
# Generated by Nimbus
|
|
545
|
-
# Configure your backend before running terraform init
|
|
546
|
-
|
|
547
|
-
# terraform {
|
|
548
|
-
# backend "${config.provider === 'gcp' ? 'gcs' : 'azurerm'}" {
|
|
549
|
-
# # Configure your backend settings
|
|
550
|
-
# }
|
|
551
|
-
# }
|
|
552
|
-
`,
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
private generateTfvarsExample(config: TerraformProjectConfig): GeneratedFile {
|
|
557
|
-
const lines = [
|
|
558
|
-
`# ${config.projectName} - Example Variables`,
|
|
559
|
-
'# Copy this file and customize for your environment',
|
|
560
|
-
'# Generated by Nimbus',
|
|
561
|
-
'',
|
|
562
|
-
`project_name = "${config.projectName}"`,
|
|
563
|
-
'environment = "dev"',
|
|
564
|
-
`region = "${config.region}"`,
|
|
565
|
-
'',
|
|
566
|
-
];
|
|
567
|
-
|
|
568
|
-
if (config.components.includes('vpc')) {
|
|
569
|
-
lines.push('vpc_cidr = "10.0.0.0/16"');
|
|
570
|
-
}
|
|
571
|
-
if (config.components.includes('eks')) {
|
|
572
|
-
lines.push('node_instance_type = "t3.medium"');
|
|
573
|
-
lines.push('node_count = 2');
|
|
574
|
-
}
|
|
575
|
-
if (config.components.includes('rds')) {
|
|
576
|
-
lines.push('db_instance_class = "db.t3.micro"');
|
|
577
|
-
}
|
|
578
|
-
if (config.components.includes('ecs')) {
|
|
579
|
-
lines.push('container_image = "nginx:latest"');
|
|
580
|
-
lines.push('container_port = 8080');
|
|
581
|
-
lines.push('ecs_cpu = 256');
|
|
582
|
-
lines.push('ecs_memory = 512');
|
|
583
|
-
lines.push('desired_count = 2');
|
|
584
|
-
}
|
|
585
|
-
if (config.components.includes('kms')) {
|
|
586
|
-
lines.push(`kms_key_alias = "${config.projectName}-key"`);
|
|
587
|
-
lines.push('kms_deletion_window = 30');
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
return { path: 'terraform.tfvars.example', content: `${lines.join('\n')}\n` };
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
private generateReadme(config: TerraformProjectConfig): GeneratedFile {
|
|
594
|
-
return {
|
|
595
|
-
path: 'README.md',
|
|
596
|
-
content: `# ${config.projectName}
|
|
597
|
-
|
|
598
|
-
Infrastructure as Code managed by Terraform. Generated by Nimbus.
|
|
599
|
-
|
|
600
|
-
## Components
|
|
601
|
-
${config.components.map(c => `- ${c.toUpperCase()}`).join('\n')}
|
|
602
|
-
|
|
603
|
-
## Environments
|
|
604
|
-
- \`dev\` - Development environment
|
|
605
|
-
- \`staging\` - Staging environment
|
|
606
|
-
- \`prod\` - Production environment
|
|
607
|
-
|
|
608
|
-
## Usage
|
|
609
|
-
|
|
610
|
-
\`\`\`bash
|
|
611
|
-
# Initialize
|
|
612
|
-
terraform init
|
|
613
|
-
|
|
614
|
-
# Plan with environment-specific vars
|
|
615
|
-
terraform plan -var-file="environments/dev/terraform.tfvars"
|
|
616
|
-
|
|
617
|
-
# Apply
|
|
618
|
-
terraform apply -var-file="environments/dev/terraform.tfvars"
|
|
619
|
-
\`\`\`
|
|
620
|
-
|
|
621
|
-
## Structure
|
|
622
|
-
\`\`\`
|
|
623
|
-
.
|
|
624
|
-
├── main.tf # Main configuration
|
|
625
|
-
├── variables.tf # Variable definitions
|
|
626
|
-
├── outputs.tf # Output definitions
|
|
627
|
-
├── versions.tf # Terraform and provider versions
|
|
628
|
-
├── backend.tf # Remote state configuration
|
|
629
|
-
├── terraform.tfvars.example # Example variable values
|
|
630
|
-
├── .gitignore # Git ignore rules
|
|
631
|
-
├── environments/
|
|
632
|
-
│ ├── dev/terraform.tfvars # Dev environment values
|
|
633
|
-
│ ├── staging/terraform.tfvars # Staging environment values
|
|
634
|
-
│ └── prod/terraform.tfvars # Production environment values
|
|
635
|
-
└── modules/ # Component modules
|
|
636
|
-
${config.components.map(c => ` └── ${c}/`).join('\n')}
|
|
637
|
-
\`\`\`
|
|
638
|
-
`,
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
private generateEnvTfvars(
|
|
643
|
-
config: TerraformProjectConfig,
|
|
644
|
-
env: 'dev' | 'staging' | 'prod'
|
|
645
|
-
): GeneratedFile {
|
|
646
|
-
const envConfigs = {
|
|
647
|
-
dev: {
|
|
648
|
-
instanceType: 't3.small',
|
|
649
|
-
nodeCount: 1,
|
|
650
|
-
dbClass: 'db.t3.micro',
|
|
651
|
-
dbStorage: 20,
|
|
652
|
-
azCount: 2,
|
|
653
|
-
cidr: '10.0.0.0/16',
|
|
654
|
-
ecsCpu: 256,
|
|
655
|
-
ecsMemory: 512,
|
|
656
|
-
ecsDesiredCount: 1,
|
|
657
|
-
},
|
|
658
|
-
staging: {
|
|
659
|
-
instanceType: 't3.medium',
|
|
660
|
-
nodeCount: 2,
|
|
661
|
-
dbClass: 'db.t3.small',
|
|
662
|
-
dbStorage: 50,
|
|
663
|
-
azCount: 2,
|
|
664
|
-
cidr: '10.1.0.0/16',
|
|
665
|
-
ecsCpu: 512,
|
|
666
|
-
ecsMemory: 1024,
|
|
667
|
-
ecsDesiredCount: 2,
|
|
668
|
-
},
|
|
669
|
-
prod: {
|
|
670
|
-
instanceType: 't3.large',
|
|
671
|
-
nodeCount: 3,
|
|
672
|
-
dbClass: 'db.r6g.large',
|
|
673
|
-
dbStorage: 100,
|
|
674
|
-
azCount: 3,
|
|
675
|
-
cidr: '10.2.0.0/16',
|
|
676
|
-
ecsCpu: 1024,
|
|
677
|
-
ecsMemory: 2048,
|
|
678
|
-
ecsDesiredCount: 3,
|
|
679
|
-
},
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
const c = envConfigs[env];
|
|
683
|
-
const azs = Array.from(
|
|
684
|
-
{ length: c.azCount },
|
|
685
|
-
(_, i) => `"${config.region}${String.fromCharCode(97 + i)}"`
|
|
686
|
-
);
|
|
687
|
-
|
|
688
|
-
const lines = [
|
|
689
|
-
`# ${config.projectName} - ${env.charAt(0).toUpperCase() + env.slice(1)} Environment`,
|
|
690
|
-
'# Generated by Nimbus',
|
|
691
|
-
'',
|
|
692
|
-
`project_name = "${config.projectName}"`,
|
|
693
|
-
`environment = "${env}"`,
|
|
694
|
-
`region = "${config.region}"`,
|
|
695
|
-
'',
|
|
696
|
-
'tags = {',
|
|
697
|
-
` Project = "${config.projectName}"`,
|
|
698
|
-
` Environment = "${env}"`,
|
|
699
|
-
' ManagedBy = "terraform"',
|
|
700
|
-
'}',
|
|
701
|
-
'',
|
|
702
|
-
];
|
|
703
|
-
|
|
704
|
-
if (config.components.includes('vpc')) {
|
|
705
|
-
lines.push(`vpc_cidr = "${c.cidr}"`);
|
|
706
|
-
lines.push(`availability_zones = [${azs.join(', ')}]`);
|
|
707
|
-
lines.push('');
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (config.components.includes('eks')) {
|
|
711
|
-
lines.push(`node_instance_type = "${c.instanceType}"`);
|
|
712
|
-
lines.push(`node_count = ${c.nodeCount}`);
|
|
713
|
-
lines.push('');
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (config.components.includes('rds')) {
|
|
717
|
-
lines.push(`db_instance_class = "${c.dbClass}"`);
|
|
718
|
-
lines.push(`db_storage_size = ${c.dbStorage}`);
|
|
719
|
-
lines.push('');
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (config.components.includes('ecs')) {
|
|
723
|
-
lines.push(`ecs_cpu = ${c.ecsCpu}`);
|
|
724
|
-
lines.push(`ecs_memory = ${c.ecsMemory}`);
|
|
725
|
-
lines.push(`desired_count = ${c.ecsDesiredCount}`);
|
|
726
|
-
lines.push('');
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
if (config.components.includes('kms')) {
|
|
730
|
-
lines.push(`kms_key_alias = "${config.projectName}-key"`);
|
|
731
|
-
lines.push(`kms_deletion_window = ${env === 'prod' ? 30 : 7}`);
|
|
732
|
-
lines.push('');
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
return {
|
|
736
|
-
path: `environments/${env}/terraform.tfvars`,
|
|
737
|
-
content: lines.join('\n'),
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
private generateModuleFiles(config: TerraformProjectConfig, component: string): GeneratedFile[] {
|
|
742
|
-
const files: GeneratedFile[] = [];
|
|
743
|
-
|
|
744
|
-
files.push({
|
|
745
|
-
path: `modules/${component}/main.tf`,
|
|
746
|
-
content: this.getModuleMainTf(config, component),
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
files.push({
|
|
750
|
-
path: `modules/${component}/variables.tf`,
|
|
751
|
-
content: this.getModuleVariablesTf(component),
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
files.push({
|
|
755
|
-
path: `modules/${component}/outputs.tf`,
|
|
756
|
-
content: this.getModuleOutputsTf(component),
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
return files;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// ===== Validation Pipeline (Gap #9 + #12) =====
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Validate a set of generated Terraform files.
|
|
766
|
-
* Runs structural, syntactic, and best-practice checks similar to tflint.
|
|
767
|
-
*/
|
|
768
|
-
validateProject(files: GeneratedFile[], _config?: TerraformProjectConfig): ValidationReport {
|
|
769
|
-
const items: ValidationItem[] = [];
|
|
770
|
-
|
|
771
|
-
// 1. Check required files are present
|
|
772
|
-
items.push(...this.checkRequiredFiles(files));
|
|
773
|
-
|
|
774
|
-
// 2. Basic HCL syntax validation
|
|
775
|
-
items.push(...this.checkHclSyntax(files));
|
|
776
|
-
|
|
777
|
-
// 3. Check for anti-patterns (tflint-style)
|
|
778
|
-
items.push(...this.checkAntiPatterns(files));
|
|
779
|
-
|
|
780
|
-
// 4. Check for missing tags on resources
|
|
781
|
-
items.push(...this.checkMissingTags(files));
|
|
782
|
-
|
|
783
|
-
const errors = items.filter(i => i.severity === 'error').length;
|
|
784
|
-
const warnings = items.filter(i => i.severity === 'warning').length;
|
|
785
|
-
const info = items.filter(i => i.severity === 'info').length;
|
|
786
|
-
|
|
787
|
-
return {
|
|
788
|
-
valid: errors === 0,
|
|
789
|
-
items,
|
|
790
|
-
summary: { errors, warnings, info },
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
private checkRequiredFiles(files: GeneratedFile[]): ValidationItem[] {
|
|
795
|
-
const items: ValidationItem[] = [];
|
|
796
|
-
const requiredFiles = ['main.tf', 'variables.tf', 'outputs.tf', 'versions.tf', 'backend.tf'];
|
|
797
|
-
const filePaths = files.map(f => f.path);
|
|
798
|
-
|
|
799
|
-
for (const required of requiredFiles) {
|
|
800
|
-
if (!filePaths.includes(required)) {
|
|
801
|
-
items.push({
|
|
802
|
-
severity: 'error',
|
|
803
|
-
message: `Required file missing: ${required}`,
|
|
804
|
-
rule: 'required-files',
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Check environment files
|
|
810
|
-
for (const env of ['dev', 'staging', 'prod']) {
|
|
811
|
-
if (!filePaths.includes(`environments/${env}/terraform.tfvars`)) {
|
|
812
|
-
items.push({
|
|
813
|
-
severity: 'warning',
|
|
814
|
-
message: `Missing environment tfvars: environments/${env}/terraform.tfvars`,
|
|
815
|
-
rule: 'env-separation',
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
return items;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
private checkHclSyntax(files: GeneratedFile[]): ValidationItem[] {
|
|
824
|
-
const items: ValidationItem[] = [];
|
|
825
|
-
|
|
826
|
-
for (const file of files) {
|
|
827
|
-
if (!file.path.endsWith('.tf')) {
|
|
828
|
-
continue;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// Check matching braces
|
|
832
|
-
const openBraces = (file.content.match(/\{/g) || []).length;
|
|
833
|
-
const closeBraces = (file.content.match(/\}/g) || []).length;
|
|
834
|
-
if (openBraces !== closeBraces) {
|
|
835
|
-
items.push({
|
|
836
|
-
severity: 'error',
|
|
837
|
-
message: `Mismatched braces: ${openBraces} open, ${closeBraces} close`,
|
|
838
|
-
file: file.path,
|
|
839
|
-
rule: 'hcl-syntax',
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// Check matching quotes
|
|
844
|
-
const quoteCount = (file.content.match(/"/g) || []).length;
|
|
845
|
-
if (quoteCount % 2 !== 0) {
|
|
846
|
-
items.push({
|
|
847
|
-
severity: 'error',
|
|
848
|
-
message: 'Unmatched quotes detected',
|
|
849
|
-
file: file.path,
|
|
850
|
-
rule: 'hcl-syntax',
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// Check for valid resource/module/variable declarations
|
|
855
|
-
const lines = file.content.split('\n');
|
|
856
|
-
for (let i = 0; i < lines.length; i++) {
|
|
857
|
-
const line = lines[i].trim();
|
|
858
|
-
if (
|
|
859
|
-
line.startsWith('resource ') ||
|
|
860
|
-
line.startsWith('module ') ||
|
|
861
|
-
line.startsWith('variable ')
|
|
862
|
-
) {
|
|
863
|
-
if (!line.includes('{') && !line.includes('"')) {
|
|
864
|
-
items.push({
|
|
865
|
-
severity: 'warning',
|
|
866
|
-
message: `Potentially malformed declaration: ${line.substring(0, 60)}`,
|
|
867
|
-
file: file.path,
|
|
868
|
-
line: i + 1,
|
|
869
|
-
rule: 'hcl-syntax',
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
return items;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
private checkAntiPatterns(files: GeneratedFile[]): ValidationItem[] {
|
|
880
|
-
const items: ValidationItem[] = [];
|
|
881
|
-
|
|
882
|
-
for (const file of files) {
|
|
883
|
-
if (!file.path.endsWith('.tf')) {
|
|
884
|
-
continue;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
const lines = file.content.split('\n');
|
|
888
|
-
for (let i = 0; i < lines.length; i++) {
|
|
889
|
-
const line = lines[i];
|
|
890
|
-
|
|
891
|
-
// Check for hardcoded AWS account IDs (12-digit numbers)
|
|
892
|
-
if (/\d{12}/.test(line) && !line.trim().startsWith('#')) {
|
|
893
|
-
items.push({
|
|
894
|
-
severity: 'warning',
|
|
895
|
-
message: 'Possible hardcoded AWS account ID',
|
|
896
|
-
file: file.path,
|
|
897
|
-
line: i + 1,
|
|
898
|
-
rule: 'no-hardcoded-values',
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// Check for hardcoded secrets/passwords
|
|
903
|
-
if (
|
|
904
|
-
/password\s*=\s*"[^"]*[^v][^a][^r]/.test(line.toLowerCase()) &&
|
|
905
|
-
!line.trim().startsWith('#')
|
|
906
|
-
) {
|
|
907
|
-
items.push({
|
|
908
|
-
severity: 'error',
|
|
909
|
-
message: 'Possible hardcoded password',
|
|
910
|
-
file: file.path,
|
|
911
|
-
line: i + 1,
|
|
912
|
-
rule: 'no-hardcoded-secrets',
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// Check for publicly accessible resources
|
|
917
|
-
if (/publicly_accessible\s*=\s*true/.test(line) && !line.trim().startsWith('#')) {
|
|
918
|
-
items.push({
|
|
919
|
-
severity: 'warning',
|
|
920
|
-
message: 'Resource is publicly accessible',
|
|
921
|
-
file: file.path,
|
|
922
|
-
line: i + 1,
|
|
923
|
-
rule: 'no-public-access',
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
return items;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
private checkMissingTags(files: GeneratedFile[]): ValidationItem[] {
|
|
933
|
-
const items: ValidationItem[] = [];
|
|
934
|
-
|
|
935
|
-
for (const file of files) {
|
|
936
|
-
if (
|
|
937
|
-
!file.path.endsWith('.tf') ||
|
|
938
|
-
file.path.includes('variables') ||
|
|
939
|
-
file.path.includes('outputs') ||
|
|
940
|
-
file.path.includes('versions')
|
|
941
|
-
) {
|
|
942
|
-
continue;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// Check if resource blocks have tags
|
|
946
|
-
const hasResources = /resource\s+"/.test(file.content);
|
|
947
|
-
const hasTags = /tags\s*=/.test(file.content) || /tags\s*\{/.test(file.content);
|
|
948
|
-
|
|
949
|
-
if (hasResources && !hasTags) {
|
|
950
|
-
items.push({
|
|
951
|
-
severity: 'warning',
|
|
952
|
-
message: 'Resource blocks without tags attribute',
|
|
953
|
-
file: file.path,
|
|
954
|
-
rule: 'require-tags',
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
return items;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// ===== Subprocess Validation (D1) =====
|
|
963
|
-
|
|
964
|
-
/**
|
|
965
|
-
* Validate generated Terraform files by writing them to a temp directory and
|
|
966
|
-
* running real CLI tools: `terraform fmt`, `terraform validate`, and optionally `tflint`.
|
|
967
|
-
*
|
|
968
|
-
* This is a best-effort operation. If the terraform binary is not installed, the
|
|
969
|
-
* individual SubprocessResult entries will contain the error in stderr and
|
|
970
|
-
* success will be false. The caller (generate()) catches any top-level throw
|
|
971
|
-
* so that subprocess validation never blocks project generation.
|
|
972
|
-
*/
|
|
973
|
-
async validateWithSubprocess(
|
|
974
|
-
files: GeneratedFile[],
|
|
975
|
-
validationMode: 'required' | 'optional' = 'required'
|
|
976
|
-
): Promise<SubprocessValidation> {
|
|
977
|
-
const { mkdtempSync, writeFileSync, mkdirSync, rmSync } = await import('node:fs');
|
|
978
|
-
const { join, dirname } = await import('node:path');
|
|
979
|
-
const { tmpdir } = await import('node:os');
|
|
980
|
-
|
|
981
|
-
const tmpDir = mkdtempSync(join(tmpdir(), 'nimbus-tf-validate-'));
|
|
982
|
-
|
|
983
|
-
try {
|
|
984
|
-
// Write all generated files into the temp directory
|
|
985
|
-
for (const file of files) {
|
|
986
|
-
const filePath = join(tmpDir, file.path);
|
|
987
|
-
const dir = dirname(filePath);
|
|
988
|
-
mkdirSync(dir, { recursive: true });
|
|
989
|
-
writeFileSync(filePath, file.content, 'utf-8');
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// 1. terraform fmt -check -diff
|
|
993
|
-
const fmtCheck = await this.runCommand('terraform', ['fmt', '-check', '-diff', tmpDir]);
|
|
994
|
-
|
|
995
|
-
// 2. terraform init -backend=false + terraform validate
|
|
996
|
-
let terraformValidate: SubprocessResult;
|
|
997
|
-
const initResult = await this.runCommand('terraform', [
|
|
998
|
-
`-chdir=${tmpDir}`,
|
|
999
|
-
'init',
|
|
1000
|
-
'-backend=false',
|
|
1001
|
-
]);
|
|
1002
|
-
|
|
1003
|
-
if (initResult.success) {
|
|
1004
|
-
terraformValidate = await this.runCommand('terraform', [`-chdir=${tmpDir}`, 'validate']);
|
|
1005
|
-
} else {
|
|
1006
|
-
terraformValidate = {
|
|
1007
|
-
success: false,
|
|
1008
|
-
stdout: '',
|
|
1009
|
-
stderr: `init failed: ${initResult.stderr}`,
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
// 3. tflint
|
|
1014
|
-
let tflint: SubprocessResult | null = null;
|
|
1015
|
-
const tflintInstalled = await this.commandExists('tflint');
|
|
1016
|
-
if (tflintInstalled) {
|
|
1017
|
-
tflint = await this.runCommand('tflint', [`--chdir=${tmpDir}`]);
|
|
1018
|
-
} else if (validationMode === 'required') {
|
|
1019
|
-
tflint = {
|
|
1020
|
-
success: false,
|
|
1021
|
-
stdout: '',
|
|
1022
|
-
stderr: 'tflint is required but not installed. Install: brew install tflint',
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
// 4. checkov
|
|
1027
|
-
let checkov: SubprocessResult | null = null;
|
|
1028
|
-
const checkovInstalled = await this.commandExists('checkov');
|
|
1029
|
-
if (checkovInstalled) {
|
|
1030
|
-
checkov = await this.runCommand('checkov', [
|
|
1031
|
-
'-d',
|
|
1032
|
-
tmpDir,
|
|
1033
|
-
'--framework',
|
|
1034
|
-
'terraform',
|
|
1035
|
-
'--quiet',
|
|
1036
|
-
'--compact',
|
|
1037
|
-
]);
|
|
1038
|
-
} else if (validationMode === 'required') {
|
|
1039
|
-
checkov = {
|
|
1040
|
-
success: false,
|
|
1041
|
-
stdout: '',
|
|
1042
|
-
stderr: 'checkov is required but not installed. Install: pip install checkov',
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
return { fmtCheck, terraformValidate, tflint, checkov };
|
|
1047
|
-
} finally {
|
|
1048
|
-
// Always clean up the temp directory
|
|
1049
|
-
try {
|
|
1050
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
1051
|
-
} catch {
|
|
1052
|
-
// Best-effort cleanup
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
/**
|
|
1058
|
-
* Run an external command and capture its stdout, stderr, and exit code.
|
|
1059
|
-
* Uses child_process.spawn for subprocess execution with a configurable timeout
|
|
1060
|
-
* (default 10 seconds) to prevent blocking on slow network operations
|
|
1061
|
-
* such as `terraform init` downloading providers.
|
|
1062
|
-
*/
|
|
1063
|
-
private async runCommand(
|
|
1064
|
-
cmd: string,
|
|
1065
|
-
args: string[],
|
|
1066
|
-
timeoutMs: number = 3_000
|
|
1067
|
-
): Promise<SubprocessResult> {
|
|
1068
|
-
try {
|
|
1069
|
-
const result = await new Promise<SubprocessResult>(resolve => {
|
|
1070
|
-
const proc = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1071
|
-
let stdout = '';
|
|
1072
|
-
let stderr = '';
|
|
1073
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
1074
|
-
stdout += data.toString();
|
|
1075
|
-
});
|
|
1076
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
1077
|
-
stderr += data.toString();
|
|
1078
|
-
});
|
|
1079
|
-
proc.on('close', code => {
|
|
1080
|
-
resolve({ success: code === 0, stdout, stderr });
|
|
1081
|
-
});
|
|
1082
|
-
proc.on('error', err => {
|
|
1083
|
-
resolve({ success: false, stdout, stderr: stderr || err.message });
|
|
1084
|
-
});
|
|
1085
|
-
// Timeout guard
|
|
1086
|
-
setTimeout(() => {
|
|
1087
|
-
try {
|
|
1088
|
-
proc.kill('SIGTERM');
|
|
1089
|
-
} catch {
|
|
1090
|
-
/* already exited */
|
|
1091
|
-
}
|
|
1092
|
-
resolve({
|
|
1093
|
-
success: false,
|
|
1094
|
-
stdout: '',
|
|
1095
|
-
stderr: `Command timed out after ${timeoutMs}ms`,
|
|
1096
|
-
});
|
|
1097
|
-
}, timeoutMs);
|
|
1098
|
-
});
|
|
1099
|
-
|
|
1100
|
-
return result;
|
|
1101
|
-
} catch (error) {
|
|
1102
|
-
return {
|
|
1103
|
-
success: false,
|
|
1104
|
-
stdout: '',
|
|
1105
|
-
stderr: (error as Error).message,
|
|
1106
|
-
};
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
/**
|
|
1111
|
-
* Check if a command exists on PATH by running `which`.
|
|
1112
|
-
*/
|
|
1113
|
-
private async commandExists(cmd: string): Promise<boolean> {
|
|
1114
|
-
try {
|
|
1115
|
-
const result = await new Promise<number>(resolve => {
|
|
1116
|
-
const proc = spawn('which', [cmd], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1117
|
-
proc.on('close', code => {
|
|
1118
|
-
resolve(code ?? 1);
|
|
1119
|
-
});
|
|
1120
|
-
proc.on('error', () => {
|
|
1121
|
-
resolve(1);
|
|
1122
|
-
});
|
|
1123
|
-
});
|
|
1124
|
-
return result === 0;
|
|
1125
|
-
} catch {
|
|
1126
|
-
return false;
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
// ===== Helper Methods =====
|
|
1131
|
-
|
|
1132
|
-
private getProviderBlock(config: TerraformProjectConfig): string {
|
|
1133
|
-
if (config.provider === 'aws') {
|
|
1134
|
-
return `provider "aws" {
|
|
1135
|
-
region = var.region
|
|
1136
|
-
|
|
1137
|
-
default_tags {
|
|
1138
|
-
tags = var.tags
|
|
1139
|
-
}
|
|
1140
|
-
}`;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
if (config.provider === 'gcp') {
|
|
1144
|
-
return `provider "google" {
|
|
1145
|
-
region = var.region
|
|
1146
|
-
project = var.project_name
|
|
1147
|
-
}`;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
return `provider "azurerm" {
|
|
1151
|
-
features {}
|
|
1152
|
-
}`;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
private getModuleBlock(config: TerraformProjectConfig, component: string): string {
|
|
1156
|
-
const commonVars = ` project_name = var.project_name
|
|
1157
|
-
environment = var.environment
|
|
1158
|
-
tags = var.tags`;
|
|
1159
|
-
|
|
1160
|
-
switch (component) {
|
|
1161
|
-
case 'vpc':
|
|
1162
|
-
return `module "vpc" {
|
|
1163
|
-
source = "./modules/vpc"
|
|
1164
|
-
|
|
1165
|
-
${commonVars}
|
|
1166
|
-
vpc_cidr = var.vpc_cidr
|
|
1167
|
-
availability_zones = var.availability_zones
|
|
1168
|
-
}`;
|
|
1169
|
-
|
|
1170
|
-
case 'eks':
|
|
1171
|
-
return `module "eks" {
|
|
1172
|
-
source = "./modules/eks"
|
|
1173
|
-
|
|
1174
|
-
${commonVars}
|
|
1175
|
-
vpc_id = module.vpc.vpc_id
|
|
1176
|
-
subnet_ids = module.vpc.private_subnet_ids
|
|
1177
|
-
cluster_version = var.cluster_version
|
|
1178
|
-
node_instance_type = var.node_instance_type
|
|
1179
|
-
node_count = var.node_count
|
|
1180
|
-
|
|
1181
|
-
depends_on = [module.vpc]
|
|
1182
|
-
}`;
|
|
1183
|
-
|
|
1184
|
-
case 'rds':
|
|
1185
|
-
return `module "rds" {
|
|
1186
|
-
source = "./modules/rds"
|
|
1187
|
-
|
|
1188
|
-
${commonVars}
|
|
1189
|
-
vpc_id = module.vpc.vpc_id
|
|
1190
|
-
subnet_ids = module.vpc.private_subnet_ids
|
|
1191
|
-
instance_class = var.db_instance_class
|
|
1192
|
-
engine = var.db_engine
|
|
1193
|
-
storage_size = var.db_storage_size
|
|
1194
|
-
|
|
1195
|
-
depends_on = [module.vpc]
|
|
1196
|
-
}`;
|
|
1197
|
-
|
|
1198
|
-
case 's3':
|
|
1199
|
-
return `module "s3" {
|
|
1200
|
-
source = "./modules/s3"
|
|
1201
|
-
|
|
1202
|
-
${commonVars}
|
|
1203
|
-
bucket_name = var.bucket_name
|
|
1204
|
-
}`;
|
|
1205
|
-
|
|
1206
|
-
case 'ecs':
|
|
1207
|
-
return `module "ecs" {
|
|
1208
|
-
source = "./modules/ecs"
|
|
1209
|
-
|
|
1210
|
-
${commonVars}
|
|
1211
|
-
vpc_id = module.vpc.vpc_id
|
|
1212
|
-
public_subnet_ids = module.vpc.public_subnet_ids
|
|
1213
|
-
private_subnet_ids = module.vpc.private_subnet_ids
|
|
1214
|
-
container_image = var.container_image
|
|
1215
|
-
container_port = var.container_port
|
|
1216
|
-
cpu = var.ecs_cpu
|
|
1217
|
-
memory = var.ecs_memory
|
|
1218
|
-
desired_count = var.desired_count
|
|
1219
|
-
|
|
1220
|
-
depends_on = [module.vpc]
|
|
1221
|
-
}`;
|
|
1222
|
-
|
|
1223
|
-
case 'kms':
|
|
1224
|
-
return `module "kms" {
|
|
1225
|
-
source = "./modules/kms"
|
|
1226
|
-
|
|
1227
|
-
${commonVars}
|
|
1228
|
-
key_alias = var.kms_key_alias
|
|
1229
|
-
deletion_window_in_days = var.kms_deletion_window
|
|
1230
|
-
}`;
|
|
1231
|
-
|
|
1232
|
-
default:
|
|
1233
|
-
return `module "${component}" {
|
|
1234
|
-
source = "./modules/${component}"
|
|
1235
|
-
|
|
1236
|
-
${commonVars}
|
|
1237
|
-
}`;
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
private getModuleMainTf(_config: TerraformProjectConfig, component: string): string {
|
|
1242
|
-
switch (component) {
|
|
1243
|
-
case 'vpc':
|
|
1244
|
-
return `# VPC Module
|
|
1245
|
-
# Generated by Nimbus
|
|
1246
|
-
|
|
1247
|
-
resource "aws_vpc" "main" {
|
|
1248
|
-
cidr_block = var.vpc_cidr
|
|
1249
|
-
enable_dns_hostnames = true
|
|
1250
|
-
enable_dns_support = true
|
|
1251
|
-
|
|
1252
|
-
tags = merge(var.tags, {
|
|
1253
|
-
Name = "\${var.project_name}-\${var.environment}-vpc"
|
|
1254
|
-
})
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
resource "aws_subnet" "private" {
|
|
1258
|
-
count = length(var.availability_zones)
|
|
1259
|
-
vpc_id = aws_vpc.main.id
|
|
1260
|
-
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
|
|
1261
|
-
availability_zone = var.availability_zones[count.index]
|
|
1262
|
-
|
|
1263
|
-
tags = merge(var.tags, {
|
|
1264
|
-
Name = "\${var.project_name}-\${var.environment}-private-\${count.index}"
|
|
1265
|
-
Type = "private"
|
|
1266
|
-
})
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
resource "aws_subnet" "public" {
|
|
1270
|
-
count = length(var.availability_zones)
|
|
1271
|
-
vpc_id = aws_vpc.main.id
|
|
1272
|
-
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + length(var.availability_zones))
|
|
1273
|
-
availability_zone = var.availability_zones[count.index]
|
|
1274
|
-
map_public_ip_on_launch = true
|
|
1275
|
-
|
|
1276
|
-
tags = merge(var.tags, {
|
|
1277
|
-
Name = "\${var.project_name}-\${var.environment}-public-\${count.index}"
|
|
1278
|
-
Type = "public"
|
|
1279
|
-
})
|
|
1280
|
-
}
|
|
1281
|
-
`;
|
|
1282
|
-
|
|
1283
|
-
case 'eks':
|
|
1284
|
-
return `# EKS Module
|
|
1285
|
-
# Generated by Nimbus
|
|
1286
|
-
|
|
1287
|
-
resource "aws_eks_cluster" "main" {
|
|
1288
|
-
name = "\${var.project_name}-\${var.environment}"
|
|
1289
|
-
role_arn = aws_iam_role.cluster.arn
|
|
1290
|
-
version = var.cluster_version
|
|
1291
|
-
|
|
1292
|
-
vpc_config {
|
|
1293
|
-
subnet_ids = var.subnet_ids
|
|
1294
|
-
endpoint_private_access = true
|
|
1295
|
-
endpoint_public_access = false
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
encryption_config {
|
|
1299
|
-
resources = ["secrets"]
|
|
1300
|
-
provider {
|
|
1301
|
-
key_arn = aws_kms_key.eks.arn
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
tags = var.tags
|
|
1306
|
-
}
|
|
1307
|
-
`;
|
|
1308
|
-
|
|
1309
|
-
case 'rds':
|
|
1310
|
-
return `# RDS Module
|
|
1311
|
-
# Generated by Nimbus
|
|
1312
|
-
|
|
1313
|
-
resource "aws_db_instance" "main" {
|
|
1314
|
-
identifier = "\${var.project_name}-\${var.environment}"
|
|
1315
|
-
instance_class = var.instance_class
|
|
1316
|
-
engine = var.engine
|
|
1317
|
-
allocated_storage = var.storage_size
|
|
1318
|
-
|
|
1319
|
-
storage_encrypted = true
|
|
1320
|
-
backup_retention_period = 7
|
|
1321
|
-
publicly_accessible = false
|
|
1322
|
-
multi_az = var.environment == "prod" ? true : false
|
|
1323
|
-
|
|
1324
|
-
db_subnet_group_name = aws_db_subnet_group.main.name
|
|
1325
|
-
|
|
1326
|
-
tags = var.tags
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
resource "aws_db_subnet_group" "main" {
|
|
1330
|
-
name = "\${var.project_name}-\${var.environment}"
|
|
1331
|
-
subnet_ids = var.subnet_ids
|
|
1332
|
-
|
|
1333
|
-
tags = var.tags
|
|
1334
|
-
}
|
|
1335
|
-
`;
|
|
1336
|
-
|
|
1337
|
-
case 's3':
|
|
1338
|
-
return `# S3 Module
|
|
1339
|
-
# Generated by Nimbus
|
|
1340
|
-
|
|
1341
|
-
resource "aws_s3_bucket" "main" {
|
|
1342
|
-
bucket = "\${var.bucket_name}-\${var.environment}"
|
|
1343
|
-
|
|
1344
|
-
tags = var.tags
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
resource "aws_s3_bucket_versioning" "main" {
|
|
1348
|
-
bucket = aws_s3_bucket.main.id
|
|
1349
|
-
versioning_configuration {
|
|
1350
|
-
status = "Enabled"
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
resource "aws_s3_bucket_server_side_encryption_configuration" "main" {
|
|
1355
|
-
bucket = aws_s3_bucket.main.id
|
|
1356
|
-
rule {
|
|
1357
|
-
apply_server_side_encryption_by_default {
|
|
1358
|
-
sse_algorithm = "aws:kms"
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
resource "aws_s3_bucket_public_access_block" "main" {
|
|
1364
|
-
bucket = aws_s3_bucket.main.id
|
|
1365
|
-
|
|
1366
|
-
block_public_acls = true
|
|
1367
|
-
block_public_policy = true
|
|
1368
|
-
ignore_public_acls = true
|
|
1369
|
-
restrict_public_buckets = true
|
|
1370
|
-
}
|
|
1371
|
-
`;
|
|
1372
|
-
|
|
1373
|
-
case 'ecs':
|
|
1374
|
-
return `# ECS Fargate Module
|
|
1375
|
-
# Generated by Nimbus
|
|
1376
|
-
|
|
1377
|
-
resource "aws_ecs_cluster" "main" {
|
|
1378
|
-
name = "\${var.project_name}-\${var.environment}"
|
|
1379
|
-
|
|
1380
|
-
setting {
|
|
1381
|
-
name = "containerInsights"
|
|
1382
|
-
value = "enabled"
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
tags = var.tags
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
resource "aws_ecs_task_definition" "main" {
|
|
1389
|
-
family = "\${var.project_name}-\${var.environment}"
|
|
1390
|
-
network_mode = "awsvpc"
|
|
1391
|
-
requires_compatibilities = ["FARGATE"]
|
|
1392
|
-
cpu = var.cpu
|
|
1393
|
-
memory = var.memory
|
|
1394
|
-
execution_role_arn = aws_iam_role.ecs_task_execution.arn
|
|
1395
|
-
task_role_arn = aws_iam_role.ecs_task.arn
|
|
1396
|
-
|
|
1397
|
-
container_definitions = jsonencode([
|
|
1398
|
-
{
|
|
1399
|
-
name = var.project_name
|
|
1400
|
-
image = var.container_image
|
|
1401
|
-
essential = true
|
|
1402
|
-
portMappings = [
|
|
1403
|
-
{
|
|
1404
|
-
containerPort = var.container_port
|
|
1405
|
-
hostPort = var.container_port
|
|
1406
|
-
protocol = "tcp"
|
|
1407
|
-
}
|
|
1408
|
-
]
|
|
1409
|
-
logConfiguration = {
|
|
1410
|
-
logDriver = "awslogs"
|
|
1411
|
-
options = {
|
|
1412
|
-
"awslogs-group" = aws_cloudwatch_log_group.ecs.name
|
|
1413
|
-
"awslogs-region" = data.aws_region.current.name
|
|
1414
|
-
"awslogs-stream-prefix" = "ecs"
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
])
|
|
1419
|
-
|
|
1420
|
-
tags = var.tags
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
resource "aws_ecs_service" "main" {
|
|
1424
|
-
name = "\${var.project_name}-\${var.environment}"
|
|
1425
|
-
cluster = aws_ecs_cluster.main.id
|
|
1426
|
-
task_definition = aws_ecs_task_definition.main.arn
|
|
1427
|
-
desired_count = var.desired_count
|
|
1428
|
-
launch_type = "FARGATE"
|
|
1429
|
-
|
|
1430
|
-
network_configuration {
|
|
1431
|
-
subnets = var.private_subnet_ids
|
|
1432
|
-
security_groups = [aws_security_group.ecs_tasks.id]
|
|
1433
|
-
assign_public_ip = false
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
load_balancer {
|
|
1437
|
-
target_group_arn = aws_lb_target_group.main.arn
|
|
1438
|
-
container_name = var.project_name
|
|
1439
|
-
container_port = var.container_port
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
deployment_circuit_breaker {
|
|
1443
|
-
enable = true
|
|
1444
|
-
rollback = true
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
tags = var.tags
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
resource "aws_lb" "main" {
|
|
1451
|
-
name = "\${var.project_name}-\${var.environment}-alb"
|
|
1452
|
-
internal = false
|
|
1453
|
-
load_balancer_type = "application"
|
|
1454
|
-
security_groups = [aws_security_group.alb.id]
|
|
1455
|
-
subnets = var.public_subnet_ids
|
|
1456
|
-
|
|
1457
|
-
tags = var.tags
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
resource "aws_lb_target_group" "main" {
|
|
1461
|
-
name = "\${var.project_name}-\${var.environment}-tg"
|
|
1462
|
-
port = var.container_port
|
|
1463
|
-
protocol = "HTTP"
|
|
1464
|
-
vpc_id = var.vpc_id
|
|
1465
|
-
target_type = "ip"
|
|
1466
|
-
|
|
1467
|
-
health_check {
|
|
1468
|
-
path = "/health"
|
|
1469
|
-
port = "traffic-port"
|
|
1470
|
-
protocol = "HTTP"
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
tags = var.tags
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
resource "aws_lb_listener" "http" {
|
|
1477
|
-
load_balancer_arn = aws_lb.main.arn
|
|
1478
|
-
port = "80"
|
|
1479
|
-
protocol = "HTTP"
|
|
1480
|
-
|
|
1481
|
-
default_action {
|
|
1482
|
-
type = "forward"
|
|
1483
|
-
target_group_arn = aws_lb_target_group.main.arn
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
resource "aws_security_group" "alb" {
|
|
1488
|
-
name = "\${var.project_name}-\${var.environment}-alb-sg"
|
|
1489
|
-
description = "ALB security group"
|
|
1490
|
-
vpc_id = var.vpc_id
|
|
1491
|
-
|
|
1492
|
-
ingress {
|
|
1493
|
-
from_port = 80
|
|
1494
|
-
to_port = 80
|
|
1495
|
-
protocol = "tcp"
|
|
1496
|
-
cidr_blocks = ["0.0.0.0/0"]
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
ingress {
|
|
1500
|
-
from_port = 443
|
|
1501
|
-
to_port = 443
|
|
1502
|
-
protocol = "tcp"
|
|
1503
|
-
cidr_blocks = ["0.0.0.0/0"]
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
egress {
|
|
1507
|
-
from_port = 0
|
|
1508
|
-
to_port = 0
|
|
1509
|
-
protocol = "-1"
|
|
1510
|
-
cidr_blocks = ["0.0.0.0/0"]
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
tags = var.tags
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
resource "aws_security_group" "ecs_tasks" {
|
|
1517
|
-
name = "\${var.project_name}-\${var.environment}-ecs-tasks-sg"
|
|
1518
|
-
description = "ECS tasks security group"
|
|
1519
|
-
vpc_id = var.vpc_id
|
|
1520
|
-
|
|
1521
|
-
ingress {
|
|
1522
|
-
from_port = var.container_port
|
|
1523
|
-
to_port = var.container_port
|
|
1524
|
-
protocol = "tcp"
|
|
1525
|
-
security_groups = [aws_security_group.alb.id]
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
egress {
|
|
1529
|
-
from_port = 0
|
|
1530
|
-
to_port = 0
|
|
1531
|
-
protocol = "-1"
|
|
1532
|
-
cidr_blocks = ["0.0.0.0/0"]
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
tags = var.tags
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
resource "aws_iam_role" "ecs_task_execution" {
|
|
1539
|
-
name = "\${var.project_name}-\${var.environment}-ecs-exec-role"
|
|
1540
|
-
|
|
1541
|
-
assume_role_policy = jsonencode({
|
|
1542
|
-
Version = "2012-10-17"
|
|
1543
|
-
Statement = [{
|
|
1544
|
-
Action = "sts:AssumeRole"
|
|
1545
|
-
Effect = "Allow"
|
|
1546
|
-
Principal = { Service = "ecs-tasks.amazonaws.com" }
|
|
1547
|
-
}]
|
|
1548
|
-
})
|
|
1549
|
-
|
|
1550
|
-
tags = var.tags
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
resource "aws_iam_role_policy_attachment" "ecs_task_execution" {
|
|
1554
|
-
role = aws_iam_role.ecs_task_execution.name
|
|
1555
|
-
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
resource "aws_iam_role" "ecs_task" {
|
|
1559
|
-
name = "\${var.project_name}-\${var.environment}-ecs-task-role"
|
|
1560
|
-
|
|
1561
|
-
assume_role_policy = jsonencode({
|
|
1562
|
-
Version = "2012-10-17"
|
|
1563
|
-
Statement = [{
|
|
1564
|
-
Action = "sts:AssumeRole"
|
|
1565
|
-
Effect = "Allow"
|
|
1566
|
-
Principal = { Service = "ecs-tasks.amazonaws.com" }
|
|
1567
|
-
}]
|
|
1568
|
-
})
|
|
1569
|
-
|
|
1570
|
-
tags = var.tags
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
resource "aws_cloudwatch_log_group" "ecs" {
|
|
1574
|
-
name = "/ecs/\${var.project_name}-\${var.environment}"
|
|
1575
|
-
retention_in_days = 30
|
|
1576
|
-
|
|
1577
|
-
tags = var.tags
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
data "aws_region" "current" {}
|
|
1581
|
-
`;
|
|
1582
|
-
|
|
1583
|
-
case 'kms':
|
|
1584
|
-
return `# KMS Module
|
|
1585
|
-
# Generated by Nimbus
|
|
1586
|
-
|
|
1587
|
-
data "aws_caller_identity" "current" {}
|
|
1588
|
-
|
|
1589
|
-
resource "aws_kms_key" "main" {
|
|
1590
|
-
description = "KMS key for \${var.project_name} \${var.environment}"
|
|
1591
|
-
deletion_window_in_days = var.deletion_window_in_days
|
|
1592
|
-
enable_key_rotation = true
|
|
1593
|
-
|
|
1594
|
-
policy = jsonencode({
|
|
1595
|
-
Version = "2012-10-17"
|
|
1596
|
-
Statement = [
|
|
1597
|
-
{
|
|
1598
|
-
Sid = "EnableRootAccountAccess"
|
|
1599
|
-
Effect = "Allow"
|
|
1600
|
-
Principal = {
|
|
1601
|
-
AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root"
|
|
1602
|
-
}
|
|
1603
|
-
Action = "kms:*"
|
|
1604
|
-
Resource = "*"
|
|
1605
|
-
}
|
|
1606
|
-
]
|
|
1607
|
-
})
|
|
1608
|
-
|
|
1609
|
-
tags = var.tags
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
resource "aws_kms_alias" "main" {
|
|
1613
|
-
name = "alias/\${var.key_alias}"
|
|
1614
|
-
target_key_id = aws_kms_key.main.key_id
|
|
1615
|
-
}
|
|
1616
|
-
`;
|
|
1617
|
-
|
|
1618
|
-
default:
|
|
1619
|
-
return `# ${component} Module\n# Generated by Nimbus\n`;
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
private getModuleVariablesTf(component: string): string {
|
|
1624
|
-
const common = `variable "project_name" {
|
|
1625
|
-
type = string
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
variable "environment" {
|
|
1629
|
-
type = string
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
variable "tags" {
|
|
1633
|
-
type = map(string)
|
|
1634
|
-
default = {}
|
|
1635
|
-
}
|
|
1636
|
-
`;
|
|
1637
|
-
|
|
1638
|
-
switch (component) {
|
|
1639
|
-
case 'vpc':
|
|
1640
|
-
return `${common}
|
|
1641
|
-
variable "vpc_cidr" {
|
|
1642
|
-
type = string
|
|
1643
|
-
default = "10.0.0.0/16"
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
variable "availability_zones" {
|
|
1647
|
-
type = list(string)
|
|
1648
|
-
}
|
|
1649
|
-
`;
|
|
1650
|
-
|
|
1651
|
-
case 'eks':
|
|
1652
|
-
return `${common}
|
|
1653
|
-
variable "vpc_id" {
|
|
1654
|
-
type = string
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
variable "subnet_ids" {
|
|
1658
|
-
type = list(string)
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
variable "cluster_version" {
|
|
1662
|
-
type = string
|
|
1663
|
-
default = "1.28"
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
variable "node_instance_type" {
|
|
1667
|
-
type = string
|
|
1668
|
-
default = "t3.medium"
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
variable "node_count" {
|
|
1672
|
-
type = number
|
|
1673
|
-
default = 2
|
|
1674
|
-
}
|
|
1675
|
-
`;
|
|
1676
|
-
|
|
1677
|
-
case 'rds':
|
|
1678
|
-
return `${common}
|
|
1679
|
-
variable "vpc_id" {
|
|
1680
|
-
type = string
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
variable "subnet_ids" {
|
|
1684
|
-
type = list(string)
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
variable "instance_class" {
|
|
1688
|
-
type = string
|
|
1689
|
-
default = "db.t3.micro"
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
variable "engine" {
|
|
1693
|
-
type = string
|
|
1694
|
-
default = "postgres"
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
variable "storage_size" {
|
|
1698
|
-
type = number
|
|
1699
|
-
default = 20
|
|
1700
|
-
}
|
|
1701
|
-
`;
|
|
1702
|
-
|
|
1703
|
-
case 's3':
|
|
1704
|
-
return `${common}
|
|
1705
|
-
variable "bucket_name" {
|
|
1706
|
-
type = string
|
|
1707
|
-
}
|
|
1708
|
-
`;
|
|
1709
|
-
|
|
1710
|
-
case 'ecs':
|
|
1711
|
-
return `${common}
|
|
1712
|
-
variable "vpc_id" {
|
|
1713
|
-
description = "VPC ID for the ECS service"
|
|
1714
|
-
type = string
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
variable "public_subnet_ids" {
|
|
1718
|
-
description = "Public subnet IDs for the ALB"
|
|
1719
|
-
type = list(string)
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
variable "private_subnet_ids" {
|
|
1723
|
-
description = "Private subnet IDs for the ECS tasks"
|
|
1724
|
-
type = list(string)
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
variable "container_image" {
|
|
1728
|
-
description = "Docker image for the ECS task"
|
|
1729
|
-
type = string
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
variable "container_port" {
|
|
1733
|
-
description = "Port exposed by the container"
|
|
1734
|
-
type = number
|
|
1735
|
-
default = 8080
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
variable "cpu" {
|
|
1739
|
-
description = "Fargate task CPU units"
|
|
1740
|
-
type = number
|
|
1741
|
-
default = 256
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
variable "memory" {
|
|
1745
|
-
description = "Fargate task memory in MiB"
|
|
1746
|
-
type = number
|
|
1747
|
-
default = 512
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
variable "desired_count" {
|
|
1751
|
-
description = "Number of ECS tasks to run"
|
|
1752
|
-
type = number
|
|
1753
|
-
default = 2
|
|
1754
|
-
}
|
|
1755
|
-
`;
|
|
1756
|
-
|
|
1757
|
-
case 'kms':
|
|
1758
|
-
return `${common}
|
|
1759
|
-
variable "key_alias" {
|
|
1760
|
-
description = "Alias for the KMS key"
|
|
1761
|
-
type = string
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
variable "deletion_window_in_days" {
|
|
1765
|
-
description = "Number of days before the key is permanently deleted"
|
|
1766
|
-
type = number
|
|
1767
|
-
default = 30
|
|
1768
|
-
}
|
|
1769
|
-
`;
|
|
1770
|
-
|
|
1771
|
-
default:
|
|
1772
|
-
return common;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
private getModuleOutputsTf(component: string): string {
|
|
1777
|
-
switch (component) {
|
|
1778
|
-
case 'vpc':
|
|
1779
|
-
return `output "vpc_id" {
|
|
1780
|
-
value = aws_vpc.main.id
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
output "private_subnet_ids" {
|
|
1784
|
-
value = aws_subnet.private[*].id
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
output "public_subnet_ids" {
|
|
1788
|
-
value = aws_subnet.public[*].id
|
|
1789
|
-
}
|
|
1790
|
-
`;
|
|
1791
|
-
|
|
1792
|
-
case 'eks':
|
|
1793
|
-
return `output "cluster_endpoint" {
|
|
1794
|
-
value = aws_eks_cluster.main.endpoint
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
output "cluster_name" {
|
|
1798
|
-
value = aws_eks_cluster.main.name
|
|
1799
|
-
}
|
|
1800
|
-
`;
|
|
1801
|
-
|
|
1802
|
-
case 'rds':
|
|
1803
|
-
return `output "endpoint" {
|
|
1804
|
-
value = aws_db_instance.main.endpoint
|
|
1805
|
-
sensitive = true
|
|
1806
|
-
}
|
|
1807
|
-
`;
|
|
1808
|
-
|
|
1809
|
-
case 's3':
|
|
1810
|
-
return `output "bucket_arn" {
|
|
1811
|
-
value = aws_s3_bucket.main.arn
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
output "bucket_name" {
|
|
1815
|
-
value = aws_s3_bucket.main.id
|
|
1816
|
-
}
|
|
1817
|
-
`;
|
|
1818
|
-
|
|
1819
|
-
case 'ecs':
|
|
1820
|
-
return `output "cluster_name" {
|
|
1821
|
-
description = "ECS cluster name"
|
|
1822
|
-
value = aws_ecs_cluster.main.name
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
output "service_name" {
|
|
1826
|
-
description = "ECS service name"
|
|
1827
|
-
value = aws_ecs_service.main.name
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
output "alb_dns_name" {
|
|
1831
|
-
description = "ALB DNS name"
|
|
1832
|
-
value = aws_lb.main.dns_name
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
output "alb_arn" {
|
|
1836
|
-
description = "ALB ARN"
|
|
1837
|
-
value = aws_lb.main.arn
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
output "task_definition_arn" {
|
|
1841
|
-
description = "Task definition ARN"
|
|
1842
|
-
value = aws_ecs_task_definition.main.arn
|
|
1843
|
-
}
|
|
1844
|
-
`;
|
|
1845
|
-
|
|
1846
|
-
case 'kms':
|
|
1847
|
-
return `output "key_id" {
|
|
1848
|
-
description = "KMS key ID"
|
|
1849
|
-
value = aws_kms_key.main.key_id
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
output "key_arn" {
|
|
1853
|
-
description = "KMS key ARN"
|
|
1854
|
-
value = aws_kms_key.main.arn
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
output "alias_arn" {
|
|
1858
|
-
description = "KMS alias ARN"
|
|
1859
|
-
value = aws_kms_alias.main.arn
|
|
1860
|
-
}
|
|
1861
|
-
`;
|
|
1862
|
-
|
|
1863
|
-
default:
|
|
1864
|
-
return '';
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
/**
|
|
1870
|
-
* Convenience function — instantiate the generator and run it.
|
|
1871
|
-
* Used by generate-terraform.ts and aws-terraform.ts.
|
|
1872
|
-
*/
|
|
1873
|
-
export async function generateTerraformProject(config: TerraformProjectConfig): Promise<GeneratedProject> {
|
|
1874
|
-
return new TerraformProjectGenerator().generate(config);
|
|
1875
|
-
}
|