@build-astron-co/nimbus 0.2.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.
- package/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Terraform Command
|
|
3
|
+
*
|
|
4
|
+
* Generate Terraform configurations from AWS infrastructure
|
|
5
|
+
*
|
|
6
|
+
* Usage: nimbus aws terraform [options]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { logger } from '../utils';
|
|
10
|
+
import { RestClient } from '../clients';
|
|
11
|
+
import {
|
|
12
|
+
createWizard,
|
|
13
|
+
ui,
|
|
14
|
+
select,
|
|
15
|
+
confirm,
|
|
16
|
+
pathInput,
|
|
17
|
+
type WizardStep,
|
|
18
|
+
type StepResult,
|
|
19
|
+
} from '../wizard';
|
|
20
|
+
import { awsDiscoverCommand, type AwsDiscoverOptions } from './aws-discover';
|
|
21
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
import * as fs from 'fs';
|
|
24
|
+
|
|
25
|
+
// AWS Tools Service client
|
|
26
|
+
const awsToolsUrl = process.env.AWS_TOOLS_SERVICE_URL || 'http://localhost:3009';
|
|
27
|
+
const awsClient = new RestClient(awsToolsUrl);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Terraform generation context
|
|
31
|
+
*/
|
|
32
|
+
export interface AwsTerraformContext {
|
|
33
|
+
// Discovery input
|
|
34
|
+
discoverySessionId?: string;
|
|
35
|
+
resources?: DiscoveredResource[];
|
|
36
|
+
|
|
37
|
+
// Generation options
|
|
38
|
+
outputPath?: string;
|
|
39
|
+
organizeByService?: boolean;
|
|
40
|
+
generateImportBlocks?: boolean;
|
|
41
|
+
generateImportScript?: boolean;
|
|
42
|
+
terraformVersion?: string;
|
|
43
|
+
awsProviderVersion?: string;
|
|
44
|
+
|
|
45
|
+
// Starter kit options
|
|
46
|
+
includeReadme?: boolean;
|
|
47
|
+
includeGitignore?: boolean;
|
|
48
|
+
includeMakefile?: boolean;
|
|
49
|
+
|
|
50
|
+
// Output
|
|
51
|
+
terraformSessionId?: string;
|
|
52
|
+
generatedFiles?: Record<string, string>;
|
|
53
|
+
summary?: GenerationSummary;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Discovered resource
|
|
58
|
+
*/
|
|
59
|
+
interface DiscoveredResource {
|
|
60
|
+
id: string;
|
|
61
|
+
type: string;
|
|
62
|
+
region: string;
|
|
63
|
+
name?: string;
|
|
64
|
+
tags?: Record<string, string>;
|
|
65
|
+
properties: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generation summary
|
|
70
|
+
*/
|
|
71
|
+
interface GenerationSummary {
|
|
72
|
+
totalResources: number;
|
|
73
|
+
mappedResources: number;
|
|
74
|
+
unmappedResources: number;
|
|
75
|
+
filesGenerated: number;
|
|
76
|
+
servicesIncluded: string[];
|
|
77
|
+
regionsIncluded: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Command options from CLI arguments
|
|
82
|
+
*/
|
|
83
|
+
export interface AwsTerraformOptions {
|
|
84
|
+
// Discovery options (for full flow)
|
|
85
|
+
profile?: string;
|
|
86
|
+
regions?: string[];
|
|
87
|
+
services?: string[];
|
|
88
|
+
|
|
89
|
+
// Direct generation options
|
|
90
|
+
sessionId?: string; // Use existing discovery session
|
|
91
|
+
resourcesFile?: string; // Load resources from JSON file
|
|
92
|
+
|
|
93
|
+
// Generation options
|
|
94
|
+
output?: string;
|
|
95
|
+
organizeByService?: boolean;
|
|
96
|
+
importBlocks?: boolean;
|
|
97
|
+
importScript?: boolean;
|
|
98
|
+
terraformVersion?: string;
|
|
99
|
+
awsProviderVersion?: string;
|
|
100
|
+
|
|
101
|
+
// Starter kit
|
|
102
|
+
includeStarterKit?: boolean;
|
|
103
|
+
includeReadme?: boolean;
|
|
104
|
+
includeGitignore?: boolean;
|
|
105
|
+
includeMakefile?: boolean;
|
|
106
|
+
|
|
107
|
+
// Mode
|
|
108
|
+
nonInteractive?: boolean;
|
|
109
|
+
skipDiscovery?: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Run the AWS terraform command
|
|
114
|
+
*/
|
|
115
|
+
export async function awsTerraformCommand(options: AwsTerraformOptions = {}): Promise<void> {
|
|
116
|
+
logger.info('Starting AWS Terraform generation');
|
|
117
|
+
|
|
118
|
+
// Non-interactive mode
|
|
119
|
+
if (options.nonInteractive) {
|
|
120
|
+
await runNonInteractive(options);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check if we have resources to generate from
|
|
125
|
+
let resources: DiscoveredResource[] | undefined;
|
|
126
|
+
let discoverySessionId: string | undefined;
|
|
127
|
+
|
|
128
|
+
// Option 1: Use existing discovery session
|
|
129
|
+
if (options.sessionId) {
|
|
130
|
+
discoverySessionId = options.sessionId;
|
|
131
|
+
ui.info(`Using existing discovery session: ${options.sessionId}`);
|
|
132
|
+
}
|
|
133
|
+
// Option 2: Load resources from file
|
|
134
|
+
else if (options.resourcesFile) {
|
|
135
|
+
ui.startSpinner({ message: 'Loading resources from file...' });
|
|
136
|
+
try {
|
|
137
|
+
const fileContent = await readFile(options.resourcesFile, 'utf-8');
|
|
138
|
+
const data = JSON.parse(fileContent);
|
|
139
|
+
resources = data.resources || data;
|
|
140
|
+
ui.stopSpinnerSuccess(`Loaded ${resources!.length} resources from file`);
|
|
141
|
+
} catch (error: any) {
|
|
142
|
+
ui.stopSpinnerFail(`Failed to load resources: ${error.message}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Option 3: Run discovery first
|
|
147
|
+
else if (!options.skipDiscovery) {
|
|
148
|
+
const discoveryOptions: AwsDiscoverOptions = {
|
|
149
|
+
profile: options.profile,
|
|
150
|
+
regions: options.regions,
|
|
151
|
+
services: options.services,
|
|
152
|
+
nonInteractive: false,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
ui.header('nimbus aws terraform', 'Step 1: Infrastructure Discovery');
|
|
156
|
+
ui.newLine();
|
|
157
|
+
|
|
158
|
+
const inventory = await awsDiscoverCommand(discoveryOptions);
|
|
159
|
+
if (!inventory) {
|
|
160
|
+
ui.error('Discovery failed, cannot generate Terraform');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
resources = inventory.resources;
|
|
165
|
+
ui.newLine();
|
|
166
|
+
ui.header('nimbus aws terraform', 'Step 2: Terraform Generation');
|
|
167
|
+
ui.newLine();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Interactive wizard for generation options
|
|
171
|
+
const wizard = createWizard<AwsTerraformContext>({
|
|
172
|
+
title: 'Terraform Generation',
|
|
173
|
+
description: 'Configure Terraform generation options',
|
|
174
|
+
initialContext: {
|
|
175
|
+
discoverySessionId,
|
|
176
|
+
resources,
|
|
177
|
+
outputPath: options.output,
|
|
178
|
+
organizeByService: options.organizeByService ?? true,
|
|
179
|
+
generateImportBlocks: options.importBlocks ?? true,
|
|
180
|
+
generateImportScript: options.importScript ?? true,
|
|
181
|
+
terraformVersion: options.terraformVersion,
|
|
182
|
+
awsProviderVersion: options.awsProviderVersion,
|
|
183
|
+
includeReadme: options.includeReadme ?? options.includeStarterKit,
|
|
184
|
+
includeGitignore: options.includeGitignore ?? options.includeStarterKit,
|
|
185
|
+
includeMakefile: options.includeMakefile ?? options.includeStarterKit,
|
|
186
|
+
},
|
|
187
|
+
steps: createWizardSteps(!!discoverySessionId || !!resources),
|
|
188
|
+
onEvent: event => {
|
|
189
|
+
logger.debug('Wizard event', { type: event.type });
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const result = await wizard.run();
|
|
194
|
+
|
|
195
|
+
if (result.success) {
|
|
196
|
+
displayCompletionMessage(result.context);
|
|
197
|
+
} else {
|
|
198
|
+
ui.error(`Generation failed: ${result.error?.message || 'Unknown error'}`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create wizard steps
|
|
205
|
+
*/
|
|
206
|
+
function createWizardSteps(_hasResources: boolean): WizardStep<AwsTerraformContext>[] {
|
|
207
|
+
const steps: WizardStep<AwsTerraformContext>[] = [];
|
|
208
|
+
|
|
209
|
+
// Step 1: Generation Options
|
|
210
|
+
steps.push({
|
|
211
|
+
id: 'generation-options',
|
|
212
|
+
title: 'Generation Options',
|
|
213
|
+
description: 'Configure how Terraform files should be generated',
|
|
214
|
+
execute: generationOptionsStep,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Step 2: Output Location
|
|
218
|
+
steps.push({
|
|
219
|
+
id: 'output-location',
|
|
220
|
+
title: 'Output Location',
|
|
221
|
+
description: 'Where should the Terraform files be saved?',
|
|
222
|
+
execute: outputLocationStep,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Step 3: Generate
|
|
226
|
+
steps.push({
|
|
227
|
+
id: 'generate',
|
|
228
|
+
title: 'Generate Terraform',
|
|
229
|
+
description: 'Generating Terraform configurations...',
|
|
230
|
+
execute: generateStep,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Step 4: Write Files
|
|
234
|
+
steps.push({
|
|
235
|
+
id: 'write-files',
|
|
236
|
+
title: 'Write Files',
|
|
237
|
+
description: 'Writing files to disk...',
|
|
238
|
+
execute: writeFilesStep,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return steps;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Step 1: Generation Options
|
|
246
|
+
*/
|
|
247
|
+
async function generationOptionsStep(ctx: AwsTerraformContext): Promise<StepResult> {
|
|
248
|
+
// Organization style
|
|
249
|
+
const organizeChoice = await select<'service' | 'single'>({
|
|
250
|
+
message: 'How should Terraform files be organized?',
|
|
251
|
+
options: [
|
|
252
|
+
{
|
|
253
|
+
value: 'service',
|
|
254
|
+
label: 'By service (Recommended)',
|
|
255
|
+
description: 'Separate files for each AWS service (ec2.tf, s3.tf, etc.)',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
value: 'single',
|
|
259
|
+
label: 'Single file',
|
|
260
|
+
description: 'All resources in main.tf',
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
defaultValue: ctx.organizeByService !== false ? 'service' : 'single',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Import method
|
|
267
|
+
ui.newLine();
|
|
268
|
+
const importMethod = await select<'both' | 'blocks' | 'script' | 'none'>({
|
|
269
|
+
message: 'How should imports be generated?',
|
|
270
|
+
options: [
|
|
271
|
+
{
|
|
272
|
+
value: 'both',
|
|
273
|
+
label: 'Both import blocks and shell script (Recommended)',
|
|
274
|
+
description: 'Maximum compatibility with all Terraform versions',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
value: 'blocks',
|
|
278
|
+
label: 'Import blocks only (Terraform 1.5+)',
|
|
279
|
+
description: 'Modern declarative imports in .tf files',
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
value: 'script',
|
|
283
|
+
label: 'Shell script only',
|
|
284
|
+
description: 'Traditional terraform import commands',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
value: 'none',
|
|
288
|
+
label: 'No imports',
|
|
289
|
+
description: 'Generate resource definitions only',
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
defaultValue: 'both',
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Terraform version
|
|
296
|
+
ui.newLine();
|
|
297
|
+
const terraformVersion = await select({
|
|
298
|
+
message: 'Target Terraform version:',
|
|
299
|
+
options: [
|
|
300
|
+
{ value: '1.5.0', label: '1.5.0+', description: 'Supports import blocks' },
|
|
301
|
+
{ value: '1.4.0', label: '1.4.0', description: 'Latest stable without import blocks' },
|
|
302
|
+
{ value: '1.3.0', label: '1.3.0', description: 'Older version' },
|
|
303
|
+
],
|
|
304
|
+
defaultValue: ctx.terraformVersion || '1.5.0',
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Starter kit
|
|
308
|
+
ui.newLine();
|
|
309
|
+
const includeStarterKit = await confirm({
|
|
310
|
+
message: 'Include starter kit (README, .gitignore, Makefile)?',
|
|
311
|
+
defaultValue: true,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
success: true,
|
|
316
|
+
data: {
|
|
317
|
+
organizeByService: organizeChoice === 'service',
|
|
318
|
+
generateImportBlocks: importMethod === 'both' || importMethod === 'blocks',
|
|
319
|
+
generateImportScript: importMethod === 'both' || importMethod === 'script',
|
|
320
|
+
terraformVersion,
|
|
321
|
+
includeReadme: includeStarterKit,
|
|
322
|
+
includeGitignore: includeStarterKit,
|
|
323
|
+
includeMakefile: includeStarterKit,
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Step 2: Output Location
|
|
330
|
+
*/
|
|
331
|
+
async function outputLocationStep(ctx: AwsTerraformContext): Promise<StepResult> {
|
|
332
|
+
const outputPath = await pathInput(
|
|
333
|
+
'Where should the Terraform files be saved?',
|
|
334
|
+
ctx.outputPath || './terraform-aws'
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
if (!outputPath) {
|
|
338
|
+
return { success: false, error: 'Output path is required' };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Check if directory exists
|
|
342
|
+
const exists = fs.existsSync(outputPath);
|
|
343
|
+
if (exists) {
|
|
344
|
+
const files = fs.readdirSync(outputPath);
|
|
345
|
+
if (files.length > 0) {
|
|
346
|
+
ui.newLine();
|
|
347
|
+
ui.warning(`Directory ${outputPath} is not empty (${files.length} files)`);
|
|
348
|
+
|
|
349
|
+
const overwrite = await confirm({
|
|
350
|
+
message: 'Overwrite existing files?',
|
|
351
|
+
defaultValue: false,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (!overwrite) {
|
|
355
|
+
return { success: false, error: 'User cancelled - directory not empty' };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
success: true,
|
|
362
|
+
data: { outputPath },
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Step 3: Generate Terraform
|
|
368
|
+
*/
|
|
369
|
+
async function generateStep(ctx: AwsTerraformContext): Promise<StepResult> {
|
|
370
|
+
ui.startSpinner({ message: 'Generating Terraform configurations...' });
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
let response: any;
|
|
374
|
+
|
|
375
|
+
// Generate from discovery session or direct resources
|
|
376
|
+
if (ctx.discoverySessionId) {
|
|
377
|
+
response = await awsClient.post<{
|
|
378
|
+
terraformSessionId: string;
|
|
379
|
+
files: Record<string, string>;
|
|
380
|
+
summary: GenerationSummary;
|
|
381
|
+
imports: any[];
|
|
382
|
+
importScript: string;
|
|
383
|
+
}>('/api/aws/terraform/generate', {
|
|
384
|
+
sessionId: ctx.discoverySessionId,
|
|
385
|
+
options: {
|
|
386
|
+
organizeByService: ctx.organizeByService,
|
|
387
|
+
generateImportBlocks: ctx.generateImportBlocks,
|
|
388
|
+
terraformVersion: ctx.terraformVersion,
|
|
389
|
+
awsProviderVersion: ctx.awsProviderVersion,
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
} else if (ctx.resources && ctx.resources.length > 0) {
|
|
393
|
+
response = await awsClient.post<{
|
|
394
|
+
terraformSessionId: string;
|
|
395
|
+
files: Record<string, string>;
|
|
396
|
+
summary: GenerationSummary;
|
|
397
|
+
imports: any[];
|
|
398
|
+
importScript: string;
|
|
399
|
+
}>('/api/aws/terraform/generate-direct', {
|
|
400
|
+
resources: ctx.resources,
|
|
401
|
+
options: {
|
|
402
|
+
organizeByService: ctx.organizeByService,
|
|
403
|
+
generateImportBlocks: ctx.generateImportBlocks,
|
|
404
|
+
terraformVersion: ctx.terraformVersion,
|
|
405
|
+
awsProviderVersion: ctx.awsProviderVersion,
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
} else {
|
|
409
|
+
ui.stopSpinnerFail('No resources to generate from');
|
|
410
|
+
return { success: false, error: 'No resources available' };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (!response.success || !response.data) {
|
|
414
|
+
ui.stopSpinnerFail(`Generation failed: ${response.error || 'Unknown error'}`);
|
|
415
|
+
return { success: false, error: response.error || 'Generation failed' };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const { terraformSessionId, files, summary, importScript } = response.data;
|
|
419
|
+
|
|
420
|
+
ui.stopSpinnerSuccess(`Generated ${Object.keys(files).length} files`);
|
|
421
|
+
|
|
422
|
+
// Add starter kit files if requested
|
|
423
|
+
const allFiles = { ...files };
|
|
424
|
+
|
|
425
|
+
if (ctx.includeReadme) {
|
|
426
|
+
allFiles['README.md'] = generateReadme(summary);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (ctx.includeGitignore) {
|
|
430
|
+
allFiles['.gitignore'] = generateGitignore();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (ctx.includeMakefile) {
|
|
434
|
+
allFiles['Makefile'] = generateMakefile();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (ctx.generateImportScript && importScript) {
|
|
438
|
+
allFiles['import.sh'] = importScript;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
success: true,
|
|
443
|
+
data: {
|
|
444
|
+
terraformSessionId,
|
|
445
|
+
generatedFiles: allFiles,
|
|
446
|
+
summary,
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
} catch (error: any) {
|
|
450
|
+
ui.stopSpinnerFail(`Generation failed: ${error.message}`);
|
|
451
|
+
return { success: false, error: error.message };
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Step 4: Write Files
|
|
457
|
+
*/
|
|
458
|
+
async function writeFilesStep(ctx: AwsTerraformContext): Promise<StepResult> {
|
|
459
|
+
if (!ctx.generatedFiles || !ctx.outputPath) {
|
|
460
|
+
return { success: false, error: 'No files to write' };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
ui.startSpinner({ message: 'Writing files to disk...' });
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
// Create output directory
|
|
467
|
+
if (!fs.existsSync(ctx.outputPath)) {
|
|
468
|
+
fs.mkdirSync(ctx.outputPath, { recursive: true });
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Write each file
|
|
472
|
+
const fileNames = Object.keys(ctx.generatedFiles);
|
|
473
|
+
for (const fileName of fileNames) {
|
|
474
|
+
const filePath = path.join(ctx.outputPath, fileName);
|
|
475
|
+
const content = ctx.generatedFiles[fileName];
|
|
476
|
+
|
|
477
|
+
// Create subdirectories if needed
|
|
478
|
+
const dir = path.dirname(filePath);
|
|
479
|
+
if (!fs.existsSync(dir)) {
|
|
480
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
await writeFile(filePath, content, 'utf-8');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Make import script executable
|
|
487
|
+
if (ctx.generatedFiles['import.sh']) {
|
|
488
|
+
const scriptPath = path.join(ctx.outputPath, 'import.sh');
|
|
489
|
+
fs.chmodSync(scriptPath, '755');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
ui.stopSpinnerSuccess(`Wrote ${fileNames.length} files to ${ctx.outputPath}`);
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
success: true,
|
|
496
|
+
data: { filesWritten: fileNames.length },
|
|
497
|
+
};
|
|
498
|
+
} catch (error: any) {
|
|
499
|
+
ui.stopSpinnerFail(`Failed to write files: ${error.message}`);
|
|
500
|
+
return { success: false, error: error.message };
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Generate README.md content
|
|
506
|
+
*/
|
|
507
|
+
function generateReadme(summary: GenerationSummary): string {
|
|
508
|
+
return `# Terraform AWS Infrastructure
|
|
509
|
+
|
|
510
|
+
Generated by Nimbus CLI
|
|
511
|
+
|
|
512
|
+
## Summary
|
|
513
|
+
|
|
514
|
+
- **Total Resources**: ${summary.totalResources}
|
|
515
|
+
- **Mapped Resources**: ${summary.mappedResources}
|
|
516
|
+
- **Unmapped Resources**: ${summary.unmappedResources}
|
|
517
|
+
- **Files Generated**: ${summary.filesGenerated}
|
|
518
|
+
|
|
519
|
+
### Services
|
|
520
|
+
|
|
521
|
+
${summary.servicesIncluded.map(s => `- ${s}`).join('\n')}
|
|
522
|
+
|
|
523
|
+
### Regions
|
|
524
|
+
|
|
525
|
+
${summary.regionsIncluded.map(r => `- ${r}`).join('\n')}
|
|
526
|
+
|
|
527
|
+
## Getting Started
|
|
528
|
+
|
|
529
|
+
1. **Initialize Terraform**:
|
|
530
|
+
\`\`\`bash
|
|
531
|
+
terraform init
|
|
532
|
+
\`\`\`
|
|
533
|
+
|
|
534
|
+
2. **Import existing resources** (choose one):
|
|
535
|
+
|
|
536
|
+
Using import blocks (Terraform 1.5+):
|
|
537
|
+
\`\`\`bash
|
|
538
|
+
terraform plan -generate-config-out=generated.tf
|
|
539
|
+
\`\`\`
|
|
540
|
+
|
|
541
|
+
Using import script:
|
|
542
|
+
\`\`\`bash
|
|
543
|
+
./import.sh
|
|
544
|
+
\`\`\`
|
|
545
|
+
|
|
546
|
+
3. **Review the plan**:
|
|
547
|
+
\`\`\`bash
|
|
548
|
+
terraform plan
|
|
549
|
+
\`\`\`
|
|
550
|
+
|
|
551
|
+
4. **Apply changes** (should show no changes if imports were successful):
|
|
552
|
+
\`\`\`bash
|
|
553
|
+
terraform apply
|
|
554
|
+
\`\`\`
|
|
555
|
+
|
|
556
|
+
## File Structure
|
|
557
|
+
|
|
558
|
+
- \`providers.tf\` - AWS provider configuration
|
|
559
|
+
- \`variables.tf\` - Input variables
|
|
560
|
+
- \`outputs.tf\` - Output values
|
|
561
|
+
- \`*.tf\` - Resource definitions by service
|
|
562
|
+
- \`import.sh\` - Import script for existing resources
|
|
563
|
+
|
|
564
|
+
## Notes
|
|
565
|
+
|
|
566
|
+
- Review all generated configurations before applying
|
|
567
|
+
- Some sensitive values may need to be filled in manually
|
|
568
|
+
- Consider using Terraform workspaces for different environments
|
|
569
|
+
`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Generate .gitignore content
|
|
574
|
+
*/
|
|
575
|
+
function generateGitignore(): string {
|
|
576
|
+
return `# Terraform
|
|
577
|
+
*.tfstate
|
|
578
|
+
*.tfstate.*
|
|
579
|
+
.terraform/
|
|
580
|
+
.terraform.lock.hcl
|
|
581
|
+
crash.log
|
|
582
|
+
crash.*.log
|
|
583
|
+
*.tfvars
|
|
584
|
+
*.tfvars.json
|
|
585
|
+
override.tf
|
|
586
|
+
override.tf.json
|
|
587
|
+
*_override.tf
|
|
588
|
+
*_override.tf.json
|
|
589
|
+
|
|
590
|
+
# Sensitive files
|
|
591
|
+
*.pem
|
|
592
|
+
*.key
|
|
593
|
+
.env
|
|
594
|
+
.env.*
|
|
595
|
+
|
|
596
|
+
# IDE
|
|
597
|
+
.idea/
|
|
598
|
+
.vscode/
|
|
599
|
+
*.swp
|
|
600
|
+
*.swo
|
|
601
|
+
|
|
602
|
+
# OS
|
|
603
|
+
.DS_Store
|
|
604
|
+
Thumbs.db
|
|
605
|
+
`;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Generate Makefile content
|
|
610
|
+
*/
|
|
611
|
+
function generateMakefile(): string {
|
|
612
|
+
return `# Terraform Makefile
|
|
613
|
+
|
|
614
|
+
.PHONY: init plan apply destroy fmt validate import clean
|
|
615
|
+
|
|
616
|
+
# Initialize Terraform
|
|
617
|
+
init:
|
|
618
|
+
terraform init
|
|
619
|
+
|
|
620
|
+
# Plan changes
|
|
621
|
+
plan:
|
|
622
|
+
terraform plan
|
|
623
|
+
|
|
624
|
+
# Apply changes
|
|
625
|
+
apply:
|
|
626
|
+
terraform apply
|
|
627
|
+
|
|
628
|
+
# Destroy infrastructure
|
|
629
|
+
destroy:
|
|
630
|
+
terraform destroy
|
|
631
|
+
|
|
632
|
+
# Format code
|
|
633
|
+
fmt:
|
|
634
|
+
terraform fmt -recursive
|
|
635
|
+
|
|
636
|
+
# Validate configuration
|
|
637
|
+
validate:
|
|
638
|
+
terraform validate
|
|
639
|
+
|
|
640
|
+
# Import existing resources
|
|
641
|
+
import:
|
|
642
|
+
./import.sh
|
|
643
|
+
|
|
644
|
+
# Clean up
|
|
645
|
+
clean:
|
|
646
|
+
rm -rf .terraform
|
|
647
|
+
rm -f .terraform.lock.hcl
|
|
648
|
+
|
|
649
|
+
# Full workflow
|
|
650
|
+
all: init fmt validate plan
|
|
651
|
+
`;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Display completion message
|
|
656
|
+
*/
|
|
657
|
+
function displayCompletionMessage(ctx: AwsTerraformContext): void {
|
|
658
|
+
ui.newLine();
|
|
659
|
+
ui.box({
|
|
660
|
+
title: 'Terraform Generation Complete!',
|
|
661
|
+
content: [
|
|
662
|
+
`Output: ${ctx.outputPath}`,
|
|
663
|
+
`Files: ${Object.keys(ctx.generatedFiles || {}).length}`,
|
|
664
|
+
'',
|
|
665
|
+
'Summary:',
|
|
666
|
+
` Resources: ${ctx.summary?.mappedResources || 0} mapped, ${ctx.summary?.unmappedResources || 0} unmapped`,
|
|
667
|
+
` Services: ${ctx.summary?.servicesIncluded?.join(', ') || 'N/A'}`,
|
|
668
|
+
'',
|
|
669
|
+
'Next steps:',
|
|
670
|
+
` 1. cd ${ctx.outputPath}`,
|
|
671
|
+
' 2. terraform init',
|
|
672
|
+
' 3. ./import.sh # Import existing resources',
|
|
673
|
+
' 4. terraform plan',
|
|
674
|
+
],
|
|
675
|
+
style: 'rounded',
|
|
676
|
+
borderColor: 'green',
|
|
677
|
+
padding: 1,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Run in non-interactive mode
|
|
683
|
+
*/
|
|
684
|
+
async function runNonInteractive(options: AwsTerraformOptions): Promise<void> {
|
|
685
|
+
ui.header('nimbus aws terraform', 'Non-interactive mode');
|
|
686
|
+
|
|
687
|
+
// Must have either session ID, resources file, or profile for discovery
|
|
688
|
+
if (!options.sessionId && !options.resourcesFile && !options.profile) {
|
|
689
|
+
ui.error('One of --session-id, --resources-file, or --profile is required');
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
let resources: DiscoveredResource[] | undefined;
|
|
694
|
+
|
|
695
|
+
// Load from file if specified
|
|
696
|
+
if (options.resourcesFile) {
|
|
697
|
+
ui.startSpinner({ message: 'Loading resources from file...' });
|
|
698
|
+
try {
|
|
699
|
+
const fileContent = await readFile(options.resourcesFile, 'utf-8');
|
|
700
|
+
const data = JSON.parse(fileContent);
|
|
701
|
+
resources = data.resources || data;
|
|
702
|
+
ui.stopSpinnerSuccess(`Loaded ${resources!.length} resources`);
|
|
703
|
+
} catch (error: any) {
|
|
704
|
+
ui.stopSpinnerFail(`Failed to load resources: ${error.message}`);
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
// Run discovery if profile specified
|
|
709
|
+
else if (options.profile && !options.sessionId) {
|
|
710
|
+
const discoveryOptions: AwsDiscoverOptions = {
|
|
711
|
+
profile: options.profile,
|
|
712
|
+
regions: options.regions,
|
|
713
|
+
services: options.services,
|
|
714
|
+
nonInteractive: true,
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
const inventory = await awsDiscoverCommand(discoveryOptions);
|
|
718
|
+
if (!inventory) {
|
|
719
|
+
ui.error('Discovery failed');
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
722
|
+
resources = inventory.resources;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Generate Terraform
|
|
726
|
+
ui.startSpinner({ message: 'Generating Terraform configurations...' });
|
|
727
|
+
|
|
728
|
+
try {
|
|
729
|
+
let response: any;
|
|
730
|
+
|
|
731
|
+
if (options.sessionId) {
|
|
732
|
+
response = await awsClient.post('/api/aws/terraform/generate', {
|
|
733
|
+
sessionId: options.sessionId,
|
|
734
|
+
options: {
|
|
735
|
+
organizeByService: options.organizeByService ?? true,
|
|
736
|
+
generateImportBlocks: options.importBlocks ?? true,
|
|
737
|
+
terraformVersion: options.terraformVersion || '1.5.0',
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
} else if (resources) {
|
|
741
|
+
response = await awsClient.post('/api/aws/terraform/generate-direct', {
|
|
742
|
+
resources,
|
|
743
|
+
options: {
|
|
744
|
+
organizeByService: options.organizeByService ?? true,
|
|
745
|
+
generateImportBlocks: options.importBlocks ?? true,
|
|
746
|
+
terraformVersion: options.terraformVersion || '1.5.0',
|
|
747
|
+
},
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (!response.success || !response.data) {
|
|
752
|
+
ui.stopSpinnerFail('Generation failed');
|
|
753
|
+
process.exit(1);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const { files, summary, importScript } = response.data;
|
|
757
|
+
ui.stopSpinnerSuccess(`Generated ${Object.keys(files).length} files`);
|
|
758
|
+
|
|
759
|
+
// Write files
|
|
760
|
+
const outputPath = options.output || './terraform-aws';
|
|
761
|
+
ui.startSpinner({ message: 'Writing files...' });
|
|
762
|
+
|
|
763
|
+
if (!fs.existsSync(outputPath)) {
|
|
764
|
+
fs.mkdirSync(outputPath, { recursive: true });
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Add starter kit if requested
|
|
768
|
+
if (options.includeStarterKit || options.includeReadme) {
|
|
769
|
+
files['README.md'] = generateReadme(summary);
|
|
770
|
+
}
|
|
771
|
+
if (options.includeStarterKit || options.includeGitignore) {
|
|
772
|
+
files['.gitignore'] = generateGitignore();
|
|
773
|
+
}
|
|
774
|
+
if (options.includeStarterKit || options.includeMakefile) {
|
|
775
|
+
files['Makefile'] = generateMakefile();
|
|
776
|
+
}
|
|
777
|
+
if (importScript && (options.importScript ?? true)) {
|
|
778
|
+
files['import.sh'] = importScript;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
for (const [fileName, content] of Object.entries(files)) {
|
|
782
|
+
const filePath = path.join(outputPath, fileName);
|
|
783
|
+
await writeFile(filePath, content as string, 'utf-8');
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (files['import.sh']) {
|
|
787
|
+
fs.chmodSync(path.join(outputPath, 'import.sh'), '755');
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
ui.stopSpinnerSuccess(`Wrote ${Object.keys(files).length} files to ${outputPath}`);
|
|
791
|
+
|
|
792
|
+
// Show summary
|
|
793
|
+
ui.newLine();
|
|
794
|
+
ui.success('Generation complete!');
|
|
795
|
+
ui.print(` Output: ${outputPath}`);
|
|
796
|
+
ui.print(
|
|
797
|
+
` Resources: ${summary.mappedResources} mapped, ${summary.unmappedResources} unmapped`
|
|
798
|
+
);
|
|
799
|
+
} catch (error: any) {
|
|
800
|
+
ui.stopSpinnerFail(`Generation failed: ${error.message}`);
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
export default awsTerraformCommand;
|