@rulebricks/cli 1.9.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/README.md +62 -0
- package/dist/commands/clone.d.ts +6 -0
- package/dist/commands/clone.js +60 -0
- package/dist/commands/deploy.d.ts +8 -0
- package/dist/commands/deploy.js +409 -0
- package/dist/commands/destroy.d.ts +8 -0
- package/dist/commands/destroy.js +298 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +201 -0
- package/dist/commands/logs.d.ts +9 -0
- package/dist/commands/logs.js +222 -0
- package/dist/commands/open.d.ts +7 -0
- package/dist/commands/open.js +139 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.js +125 -0
- package/dist/commands/upgrade.d.ts +7 -0
- package/dist/commands/upgrade.js +239 -0
- package/dist/components/DNSWaitScreen.d.ts +9 -0
- package/dist/components/DNSWaitScreen.js +73 -0
- package/dist/components/Wizard/WizardContext.d.ts +176 -0
- package/dist/components/Wizard/WizardContext.js +346 -0
- package/dist/components/Wizard/index.d.ts +2 -0
- package/dist/components/Wizard/index.js +2 -0
- package/dist/components/Wizard/steps/CloudProviderStep.d.ts +6 -0
- package/dist/components/Wizard/steps/CloudProviderStep.js +210 -0
- package/dist/components/Wizard/steps/CredentialsStep.d.ts +6 -0
- package/dist/components/Wizard/steps/CredentialsStep.js +22 -0
- package/dist/components/Wizard/steps/DatabaseStep.d.ts +6 -0
- package/dist/components/Wizard/steps/DatabaseStep.js +80 -0
- package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +5 -0
- package/dist/components/Wizard/steps/DeploymentModeStep.js +26 -0
- package/dist/components/Wizard/steps/DomainStep.d.ts +6 -0
- package/dist/components/Wizard/steps/DomainStep.js +126 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +6 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.js +765 -0
- package/dist/components/Wizard/steps/FeaturesStep.d.ts +6 -0
- package/dist/components/Wizard/steps/FeaturesStep.js +119 -0
- package/dist/components/Wizard/steps/ReviewStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ReviewStep.js +56 -0
- package/dist/components/Wizard/steps/SMTPStep.d.ts +6 -0
- package/dist/components/Wizard/steps/SMTPStep.js +191 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.d.ts +6 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +76 -0
- package/dist/components/Wizard/steps/TierStep.d.ts +6 -0
- package/dist/components/Wizard/steps/TierStep.js +29 -0
- package/dist/components/Wizard/steps/VersionStep.d.ts +6 -0
- package/dist/components/Wizard/steps/VersionStep.js +113 -0
- package/dist/components/Wizard/steps/index.d.ts +12 -0
- package/dist/components/Wizard/steps/index.js +12 -0
- package/dist/components/common/AppShell.d.ts +31 -0
- package/dist/components/common/AppShell.js +31 -0
- package/dist/components/common/Box.d.ts +20 -0
- package/dist/components/common/Box.js +20 -0
- package/dist/components/common/Logo.d.ts +7 -0
- package/dist/components/common/Logo.js +22 -0
- package/dist/components/common/Spinner.d.ts +12 -0
- package/dist/components/common/Spinner.js +28 -0
- package/dist/components/common/index.d.ts +6 -0
- package/dist/components/common/index.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +202 -0
- package/dist/lib/cloudCli.d.ts +156 -0
- package/dist/lib/cloudCli.js +691 -0
- package/dist/lib/config.d.ts +91 -0
- package/dist/lib/config.js +278 -0
- package/dist/lib/dns.d.ts +41 -0
- package/dist/lib/dns.js +235 -0
- package/dist/lib/dockerHub.d.ts +57 -0
- package/dist/lib/dockerHub.js +128 -0
- package/dist/lib/helm.d.ts +53 -0
- package/dist/lib/helm.js +209 -0
- package/dist/lib/helmValues.d.ts +17 -0
- package/dist/lib/helmValues.js +693 -0
- package/dist/lib/kubernetes.d.ts +161 -0
- package/dist/lib/kubernetes.js +755 -0
- package/dist/lib/terraform.d.ts +44 -0
- package/dist/lib/terraform.js +230 -0
- package/dist/lib/theme.d.ts +81 -0
- package/dist/lib/theme.js +115 -0
- package/dist/lib/validation.d.ts +47 -0
- package/dist/lib/validation.js +164 -0
- package/dist/lib/versions.d.ts +69 -0
- package/dist/lib/versions.js +139 -0
- package/dist/types/index.d.ts +718 -0
- package/dist/types/index.js +556 -0
- package/email-templates/email_change.html +325 -0
- package/email-templates/invite.html +383 -0
- package/email-templates/password_change.html +414 -0
- package/email-templates/verify.html +396 -0
- package/package.json +78 -0
- package/terraform/aws/main.tf +327 -0
- package/terraform/azure/main.tf +326 -0
- package/terraform/gcp/main.tf +369 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud CLI detection and dynamic resource listing
|
|
3
|
+
*
|
|
4
|
+
* Detects installed cloud CLIs (AWS, GCP, Azure), checks authentication status,
|
|
5
|
+
* and provides functions to list regions and buckets dynamically.
|
|
6
|
+
*/
|
|
7
|
+
import { exec } from "child_process";
|
|
8
|
+
import { promisify } from "util";
|
|
9
|
+
import { CLOUD_REGIONS } from "../types/index.js";
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
// Timeout for CLI commands (in ms)
|
|
12
|
+
const CLI_TIMEOUT = 15000;
|
|
13
|
+
/**
|
|
14
|
+
* Sort regions by priority order defined in CLOUD_REGIONS.
|
|
15
|
+
* Priority regions come first (in their defined order), followed by
|
|
16
|
+
* any additional regions sorted alphabetically.
|
|
17
|
+
*/
|
|
18
|
+
function sortRegionsByPriority(regions, provider) {
|
|
19
|
+
const priorityOrder = CLOUD_REGIONS[provider];
|
|
20
|
+
const prioritySet = new Set(priorityOrder);
|
|
21
|
+
// Separate priority regions from others
|
|
22
|
+
const priorityRegions = priorityOrder.filter((r) => regions.includes(r));
|
|
23
|
+
const otherRegions = regions.filter((r) => !prioritySet.has(r)).sort();
|
|
24
|
+
return [...priorityRegions, ...otherRegions];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Execute a CLI command with timeout
|
|
28
|
+
*/
|
|
29
|
+
async function execCommand(command, timeout = CLI_TIMEOUT) {
|
|
30
|
+
try {
|
|
31
|
+
const result = await execAsync(command, { timeout });
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error && typeof error === "object" && "stdout" in error) {
|
|
36
|
+
// Command executed but returned non-zero exit code
|
|
37
|
+
const execError = error;
|
|
38
|
+
return {
|
|
39
|
+
stdout: execError.stdout || "",
|
|
40
|
+
stderr: execError.stderr || execError.message || "Command failed",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// AWS CLI
|
|
48
|
+
// ============================================================================
|
|
49
|
+
/**
|
|
50
|
+
* Check if AWS CLI is installed and authenticated
|
|
51
|
+
*/
|
|
52
|
+
export async function checkAwsCli() {
|
|
53
|
+
const status = {
|
|
54
|
+
provider: "aws",
|
|
55
|
+
installed: false,
|
|
56
|
+
authenticated: false,
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
// Check if AWS CLI is installed
|
|
60
|
+
const versionResult = await execCommand("aws --version");
|
|
61
|
+
if (versionResult.stderr && !versionResult.stdout) {
|
|
62
|
+
status.error = "AWS CLI not found";
|
|
63
|
+
return status;
|
|
64
|
+
}
|
|
65
|
+
status.installed = true;
|
|
66
|
+
// Extract version (e.g., "aws-cli/2.13.0 Python/3.11.4 ...")
|
|
67
|
+
const versionMatch = versionResult.stdout.match(/aws-cli\/([\d.]+)/);
|
|
68
|
+
status.version = versionMatch ? versionMatch[1] : undefined;
|
|
69
|
+
// Check authentication by getting caller identity
|
|
70
|
+
const identityResult = await execCommand("aws sts get-caller-identity --output json");
|
|
71
|
+
if (identityResult.stderr &&
|
|
72
|
+
identityResult.stderr.includes("Unable to locate credentials")) {
|
|
73
|
+
status.error =
|
|
74
|
+
'Not authenticated - run "aws configure" or set credentials';
|
|
75
|
+
return status;
|
|
76
|
+
}
|
|
77
|
+
if (identityResult.stderr &&
|
|
78
|
+
identityResult.stderr.includes("ExpiredToken")) {
|
|
79
|
+
status.error = "Session expired - refresh your credentials";
|
|
80
|
+
return status;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const identity = JSON.parse(identityResult.stdout);
|
|
84
|
+
status.authenticated = true;
|
|
85
|
+
status.identity = identity.Account
|
|
86
|
+
? `Account: ${identity.Account}`
|
|
87
|
+
: undefined;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
status.error = "Failed to parse identity response";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
status.error = error instanceof Error ? error.message : "Unknown error";
|
|
95
|
+
}
|
|
96
|
+
return status;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* List available AWS regions
|
|
100
|
+
*/
|
|
101
|
+
export async function listAwsRegions() {
|
|
102
|
+
try {
|
|
103
|
+
const result = await execCommand('aws ec2 describe-regions --query "Regions[].RegionName" --output json');
|
|
104
|
+
if (result.stderr && !result.stdout) {
|
|
105
|
+
return getStaticAwsRegions();
|
|
106
|
+
}
|
|
107
|
+
const regions = JSON.parse(result.stdout);
|
|
108
|
+
return sortRegionsByPriority(regions, "aws");
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return getStaticAwsRegions();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* List S3 buckets
|
|
116
|
+
*/
|
|
117
|
+
export async function listS3Buckets() {
|
|
118
|
+
try {
|
|
119
|
+
const result = await execCommand('aws s3api list-buckets --query "Buckets[].Name" --output json');
|
|
120
|
+
if (result.stderr && !result.stdout) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
const buckets = JSON.parse(result.stdout);
|
|
124
|
+
return buckets.sort();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Static fallback for AWS regions
|
|
132
|
+
*/
|
|
133
|
+
function getStaticAwsRegions() {
|
|
134
|
+
return [
|
|
135
|
+
"us-east-1",
|
|
136
|
+
"us-east-2",
|
|
137
|
+
"us-west-1",
|
|
138
|
+
"us-west-2",
|
|
139
|
+
"ap-south-1",
|
|
140
|
+
"ap-northeast-1",
|
|
141
|
+
"ap-northeast-2",
|
|
142
|
+
"ap-northeast-3",
|
|
143
|
+
"ap-southeast-1",
|
|
144
|
+
"ap-southeast-2",
|
|
145
|
+
"ca-central-1",
|
|
146
|
+
"eu-central-1",
|
|
147
|
+
"eu-west-1",
|
|
148
|
+
"eu-west-2",
|
|
149
|
+
"eu-west-3",
|
|
150
|
+
"eu-north-1",
|
|
151
|
+
"sa-east-1",
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* List EKS clusters in a specific region
|
|
156
|
+
*/
|
|
157
|
+
export async function listEksClusters(region) {
|
|
158
|
+
try {
|
|
159
|
+
const result = await execCommand(`aws eks list-clusters --region ${region} --output json`);
|
|
160
|
+
if (result.stderr && !result.stdout) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
const response = JSON.parse(result.stdout);
|
|
164
|
+
return (response.clusters || []).sort();
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// GCP CLI (gcloud)
|
|
172
|
+
// ============================================================================
|
|
173
|
+
/**
|
|
174
|
+
* Check if gcloud CLI is installed and authenticated
|
|
175
|
+
*/
|
|
176
|
+
export async function checkGcloudCli() {
|
|
177
|
+
const status = {
|
|
178
|
+
provider: "gcp",
|
|
179
|
+
installed: false,
|
|
180
|
+
authenticated: false,
|
|
181
|
+
};
|
|
182
|
+
try {
|
|
183
|
+
// Check if gcloud is installed
|
|
184
|
+
const versionResult = await execCommand("gcloud --version");
|
|
185
|
+
if (versionResult.stderr && !versionResult.stdout) {
|
|
186
|
+
status.error = "gcloud CLI not found";
|
|
187
|
+
return status;
|
|
188
|
+
}
|
|
189
|
+
status.installed = true;
|
|
190
|
+
// Extract version (e.g., "Google Cloud SDK 440.0.0")
|
|
191
|
+
const versionMatch = versionResult.stdout.match(/Google Cloud SDK ([\d.]+)/);
|
|
192
|
+
status.version = versionMatch ? versionMatch[1] : undefined;
|
|
193
|
+
// Check authentication and active project
|
|
194
|
+
const configResult = await execCommand('gcloud config list --format="json"');
|
|
195
|
+
try {
|
|
196
|
+
const config = JSON.parse(configResult.stdout);
|
|
197
|
+
const account = config.core?.account;
|
|
198
|
+
const project = config.core?.project;
|
|
199
|
+
if (!account) {
|
|
200
|
+
status.error = 'Not authenticated - run "gcloud auth login"';
|
|
201
|
+
return status;
|
|
202
|
+
}
|
|
203
|
+
status.authenticated = true;
|
|
204
|
+
status.identity = project ? `Project: ${project}` : `Account: ${account}`;
|
|
205
|
+
if (!project) {
|
|
206
|
+
status.error =
|
|
207
|
+
'No default project set - run "gcloud config set project PROJECT_ID"';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
status.error = "Failed to parse gcloud config";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
status.error = error instanceof Error ? error.message : "Unknown error";
|
|
216
|
+
}
|
|
217
|
+
return status;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get the active GCP project ID
|
|
221
|
+
*/
|
|
222
|
+
export async function getGcpProjectId() {
|
|
223
|
+
try {
|
|
224
|
+
const result = await execCommand("gcloud config get-value project");
|
|
225
|
+
const projectId = result.stdout.trim();
|
|
226
|
+
return projectId && projectId !== "(unset)" ? projectId : null;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* List available GCP regions
|
|
234
|
+
*/
|
|
235
|
+
export async function listGcpRegions() {
|
|
236
|
+
try {
|
|
237
|
+
const result = await execCommand('gcloud compute regions list --format="json(name)"');
|
|
238
|
+
if (result.stderr && !result.stdout) {
|
|
239
|
+
return getStaticGcpRegions();
|
|
240
|
+
}
|
|
241
|
+
const regions = JSON.parse(result.stdout);
|
|
242
|
+
const regionNames = regions.map((r) => r.name);
|
|
243
|
+
return sortRegionsByPriority(regionNames, "gcp");
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return getStaticGcpRegions();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* List GCS buckets
|
|
251
|
+
*/
|
|
252
|
+
export async function listGcsBuckets() {
|
|
253
|
+
try {
|
|
254
|
+
const result = await execCommand('gcloud storage buckets list --format="json(name)"');
|
|
255
|
+
if (result.stderr && !result.stdout) {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
const buckets = JSON.parse(result.stdout);
|
|
259
|
+
// Bucket names come as "gs://bucket-name", strip the prefix
|
|
260
|
+
return buckets
|
|
261
|
+
.map((b) => b.name.replace("gs://", "").replace(/\/$/, ""))
|
|
262
|
+
.sort();
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Static fallback for GCP regions
|
|
270
|
+
*/
|
|
271
|
+
function getStaticGcpRegions() {
|
|
272
|
+
return [
|
|
273
|
+
"us-central1",
|
|
274
|
+
"us-east1",
|
|
275
|
+
"us-east4",
|
|
276
|
+
"us-west1",
|
|
277
|
+
"us-west2",
|
|
278
|
+
"us-west3",
|
|
279
|
+
"us-west4",
|
|
280
|
+
"northamerica-northeast1",
|
|
281
|
+
"northamerica-northeast2",
|
|
282
|
+
"southamerica-east1",
|
|
283
|
+
"southamerica-west1",
|
|
284
|
+
"europe-central2",
|
|
285
|
+
"europe-north1",
|
|
286
|
+
"europe-west1",
|
|
287
|
+
"europe-west2",
|
|
288
|
+
"europe-west3",
|
|
289
|
+
"europe-west4",
|
|
290
|
+
"europe-west6",
|
|
291
|
+
"europe-southwest1",
|
|
292
|
+
"asia-east1",
|
|
293
|
+
"asia-east2",
|
|
294
|
+
"asia-northeast1",
|
|
295
|
+
"asia-northeast2",
|
|
296
|
+
"asia-northeast3",
|
|
297
|
+
"asia-south1",
|
|
298
|
+
"asia-south2",
|
|
299
|
+
"asia-southeast1",
|
|
300
|
+
"asia-southeast2",
|
|
301
|
+
"australia-southeast1",
|
|
302
|
+
"australia-southeast2",
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* List GKE clusters in a specific region
|
|
307
|
+
* Note: GKE supports both regional and zonal clusters. We search for regional clusters.
|
|
308
|
+
*/
|
|
309
|
+
export async function listGkeClusters(region) {
|
|
310
|
+
try {
|
|
311
|
+
// List clusters in the specified region (includes both regional and zonal clusters in that region)
|
|
312
|
+
const result = await execCommand(`gcloud container clusters list --region ${region} --format="json(name)" 2>/dev/null || gcloud container clusters list --filter="location~^${region}" --format="json(name)"`);
|
|
313
|
+
if (result.stderr && !result.stdout) {
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
const clusters = JSON.parse(result.stdout);
|
|
317
|
+
return clusters.map((c) => c.name).sort();
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// ============================================================================
|
|
324
|
+
// Azure CLI
|
|
325
|
+
// ============================================================================
|
|
326
|
+
/**
|
|
327
|
+
* Check if Azure CLI is installed and authenticated
|
|
328
|
+
*/
|
|
329
|
+
export async function checkAzureCli() {
|
|
330
|
+
const status = {
|
|
331
|
+
provider: "azure",
|
|
332
|
+
installed: false,
|
|
333
|
+
authenticated: false,
|
|
334
|
+
};
|
|
335
|
+
try {
|
|
336
|
+
// Check if az is installed
|
|
337
|
+
const versionResult = await execCommand("az --version");
|
|
338
|
+
if (versionResult.stderr && !versionResult.stdout) {
|
|
339
|
+
status.error = "Azure CLI not found";
|
|
340
|
+
return status;
|
|
341
|
+
}
|
|
342
|
+
status.installed = true;
|
|
343
|
+
// Extract version (e.g., "azure-cli 2.51.0")
|
|
344
|
+
const versionMatch = versionResult.stdout.match(/azure-cli\s+([\d.]+)/);
|
|
345
|
+
status.version = versionMatch ? versionMatch[1] : undefined;
|
|
346
|
+
// Check authentication
|
|
347
|
+
const accountResult = await execCommand("az account show --output json");
|
|
348
|
+
if (accountResult.stderr && accountResult.stderr.includes("Please run")) {
|
|
349
|
+
status.error = 'Not authenticated - run "az login"';
|
|
350
|
+
return status;
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const account = JSON.parse(accountResult.stdout);
|
|
354
|
+
status.authenticated = true;
|
|
355
|
+
status.identity = account.name
|
|
356
|
+
? `Subscription: ${account.name}`
|
|
357
|
+
: undefined;
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
status.error = "Failed to parse account info";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
status.error = error instanceof Error ? error.message : "Unknown error";
|
|
365
|
+
}
|
|
366
|
+
return status;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Get the active Azure subscription ID
|
|
370
|
+
*/
|
|
371
|
+
export async function getAzureSubscriptionId() {
|
|
372
|
+
try {
|
|
373
|
+
const result = await execCommand("az account show --query id --output tsv");
|
|
374
|
+
return result.stdout.trim() || null;
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* List available Azure regions (locations)
|
|
382
|
+
*/
|
|
383
|
+
export async function listAzureRegions() {
|
|
384
|
+
try {
|
|
385
|
+
const result = await execCommand('az account list-locations --query "[].name" --output json');
|
|
386
|
+
if (result.stderr && !result.stdout) {
|
|
387
|
+
return getStaticAzureRegions();
|
|
388
|
+
}
|
|
389
|
+
const regions = JSON.parse(result.stdout);
|
|
390
|
+
return sortRegionsByPriority(regions, "azure");
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return getStaticAzureRegions();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* List Azure storage accounts (containers require a storage account)
|
|
398
|
+
*/
|
|
399
|
+
export async function listAzureStorageAccounts() {
|
|
400
|
+
try {
|
|
401
|
+
const result = await execCommand('az storage account list --query "[].name" --output json');
|
|
402
|
+
if (result.stderr && !result.stdout) {
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
const accounts = JSON.parse(result.stdout);
|
|
406
|
+
return accounts.sort();
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return [];
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* List Azure blob containers in a storage account
|
|
414
|
+
*/
|
|
415
|
+
export async function listAzureBlobContainers(storageAccount) {
|
|
416
|
+
try {
|
|
417
|
+
const result = await execCommand(`az storage container list --account-name ${storageAccount} --auth-mode login --query "[].name" --output json`);
|
|
418
|
+
if (result.stderr && !result.stdout) {
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
const containers = JSON.parse(result.stdout);
|
|
422
|
+
return containers.sort();
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Static fallback for Azure regions
|
|
430
|
+
*/
|
|
431
|
+
function getStaticAzureRegions() {
|
|
432
|
+
return [
|
|
433
|
+
"eastus",
|
|
434
|
+
"eastus2",
|
|
435
|
+
"centralus",
|
|
436
|
+
"northcentralus",
|
|
437
|
+
"southcentralus",
|
|
438
|
+
"westus",
|
|
439
|
+
"westus2",
|
|
440
|
+
"westus3",
|
|
441
|
+
"canadacentral",
|
|
442
|
+
"canadaeast",
|
|
443
|
+
"brazilsouth",
|
|
444
|
+
"northeurope",
|
|
445
|
+
"westeurope",
|
|
446
|
+
"uksouth",
|
|
447
|
+
"ukwest",
|
|
448
|
+
"francecentral",
|
|
449
|
+
"germanywestcentral",
|
|
450
|
+
"switzerlandnorth",
|
|
451
|
+
"norwayeast",
|
|
452
|
+
"eastasia",
|
|
453
|
+
"southeastasia",
|
|
454
|
+
"japaneast",
|
|
455
|
+
"japanwest",
|
|
456
|
+
"koreacentral",
|
|
457
|
+
"australiaeast",
|
|
458
|
+
"australiasoutheast",
|
|
459
|
+
"australiacentral",
|
|
460
|
+
"centralindia",
|
|
461
|
+
"southindia",
|
|
462
|
+
"westindia",
|
|
463
|
+
"uaenorth",
|
|
464
|
+
"southafricanorth",
|
|
465
|
+
];
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* List AKS clusters, optionally filtered by resource group
|
|
469
|
+
*/
|
|
470
|
+
export async function listAksClusters(resourceGroup) {
|
|
471
|
+
try {
|
|
472
|
+
const rgFilter = resourceGroup ? ` --resource-group ${resourceGroup}` : "";
|
|
473
|
+
const result = await execCommand(`az aks list${rgFilter} --query "[].name" --output json`);
|
|
474
|
+
if (result.stderr && !result.stdout) {
|
|
475
|
+
return [];
|
|
476
|
+
}
|
|
477
|
+
const clusters = JSON.parse(result.stdout);
|
|
478
|
+
return clusters.sort();
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
return [];
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// ============================================================================
|
|
485
|
+
// Aggregated Functions
|
|
486
|
+
// ============================================================================
|
|
487
|
+
/**
|
|
488
|
+
* Check all cloud CLIs in parallel
|
|
489
|
+
*/
|
|
490
|
+
export async function checkAllCloudClis() {
|
|
491
|
+
const [aws, gcp, azure] = await Promise.all([
|
|
492
|
+
checkAwsCli(),
|
|
493
|
+
checkGcloudCli(),
|
|
494
|
+
checkAzureCli(),
|
|
495
|
+
]);
|
|
496
|
+
const anyInstalled = aws.installed || gcp.installed || azure.installed;
|
|
497
|
+
const anyAvailable = aws.authenticated || gcp.authenticated || azure.authenticated;
|
|
498
|
+
return { aws, gcp, azure, anyAvailable, anyInstalled };
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* List regions for a specific provider
|
|
502
|
+
*/
|
|
503
|
+
export async function listRegions(provider) {
|
|
504
|
+
switch (provider) {
|
|
505
|
+
case "aws":
|
|
506
|
+
return listAwsRegions();
|
|
507
|
+
case "gcp":
|
|
508
|
+
return listGcpRegions();
|
|
509
|
+
case "azure":
|
|
510
|
+
return listAzureRegions();
|
|
511
|
+
default:
|
|
512
|
+
return [];
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* List buckets/storage for a specific provider
|
|
517
|
+
*/
|
|
518
|
+
export async function listBuckets(provider) {
|
|
519
|
+
switch (provider) {
|
|
520
|
+
case "aws":
|
|
521
|
+
return listS3Buckets();
|
|
522
|
+
case "gcp":
|
|
523
|
+
return listGcsBuckets();
|
|
524
|
+
case "azure":
|
|
525
|
+
return listAzureStorageAccounts();
|
|
526
|
+
default:
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* List Kubernetes clusters for a specific provider
|
|
532
|
+
*/
|
|
533
|
+
export async function listClusters(provider, region, options) {
|
|
534
|
+
switch (provider) {
|
|
535
|
+
case "aws":
|
|
536
|
+
return listEksClusters(region);
|
|
537
|
+
case "gcp":
|
|
538
|
+
return listGkeClusters(region);
|
|
539
|
+
case "azure":
|
|
540
|
+
return listAksClusters(options?.azureResourceGroup);
|
|
541
|
+
default:
|
|
542
|
+
return [];
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Get installation URLs for cloud CLIs
|
|
547
|
+
*/
|
|
548
|
+
export const CLI_INSTALL_URLS = {
|
|
549
|
+
aws: {
|
|
550
|
+
name: "AWS CLI",
|
|
551
|
+
url: "https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html",
|
|
552
|
+
installCmd: "brew install awscli",
|
|
553
|
+
},
|
|
554
|
+
gcp: {
|
|
555
|
+
name: "Google Cloud SDK",
|
|
556
|
+
url: "https://cloud.google.com/sdk/docs/install",
|
|
557
|
+
installCmd: "brew install --cask google-cloud-sdk",
|
|
558
|
+
},
|
|
559
|
+
azure: {
|
|
560
|
+
name: "Azure CLI",
|
|
561
|
+
url: "https://docs.microsoft.com/en-us/cli/azure/install-azure-cli",
|
|
562
|
+
installCmd: "brew install azure-cli",
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
/**
|
|
566
|
+
* Get login commands for cloud CLIs
|
|
567
|
+
*/
|
|
568
|
+
export const CLI_LOGIN_COMMANDS = {
|
|
569
|
+
aws: "aws configure",
|
|
570
|
+
gcp: "gcloud auth login",
|
|
571
|
+
azure: "az login",
|
|
572
|
+
};
|
|
573
|
+
/**
|
|
574
|
+
* Check if Terraform is installed
|
|
575
|
+
*/
|
|
576
|
+
export async function checkTerraform() {
|
|
577
|
+
try {
|
|
578
|
+
const result = await execCommand("terraform --version");
|
|
579
|
+
if (result.stderr && !result.stdout) {
|
|
580
|
+
return { installed: false, error: "Terraform not found" };
|
|
581
|
+
}
|
|
582
|
+
// Extract version (e.g., "Terraform v1.5.0")
|
|
583
|
+
const versionMatch = result.stdout.match(/Terraform v([\d.]+)/);
|
|
584
|
+
return {
|
|
585
|
+
installed: true,
|
|
586
|
+
version: versionMatch ? versionMatch[1] : undefined,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
return { installed: false, error: "Terraform not found" };
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Terraform installation info
|
|
595
|
+
*/
|
|
596
|
+
export const TERRAFORM_INSTALL_INFO = {
|
|
597
|
+
name: "Terraform",
|
|
598
|
+
url: "https://developer.hashicorp.com/terraform/downloads",
|
|
599
|
+
installCmd: "brew install terraform",
|
|
600
|
+
};
|
|
601
|
+
// ============================================================================
|
|
602
|
+
// Region-filtered bucket listing
|
|
603
|
+
// ============================================================================
|
|
604
|
+
/**
|
|
605
|
+
* List S3 buckets in a specific region
|
|
606
|
+
* Note: S3 buckets are global, but we filter by region
|
|
607
|
+
*/
|
|
608
|
+
export async function listS3BucketsInRegion(region) {
|
|
609
|
+
try {
|
|
610
|
+
// First get all buckets
|
|
611
|
+
const bucketsResult = await execCommand('aws s3api list-buckets --query "Buckets[].Name" --output json');
|
|
612
|
+
if (bucketsResult.stderr && !bucketsResult.stdout) {
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
const allBuckets = JSON.parse(bucketsResult.stdout);
|
|
616
|
+
// Filter by region - check each bucket's region
|
|
617
|
+
const bucketsInRegion = [];
|
|
618
|
+
for (const bucket of allBuckets) {
|
|
619
|
+
try {
|
|
620
|
+
const locationResult = await execCommand(`aws s3api get-bucket-location --bucket ${bucket} --output json`, 5000);
|
|
621
|
+
if (locationResult.stdout) {
|
|
622
|
+
const location = JSON.parse(locationResult.stdout);
|
|
623
|
+
// null means us-east-1, otherwise it's the region name
|
|
624
|
+
const bucketRegion = location.LocationConstraint || "us-east-1";
|
|
625
|
+
if (bucketRegion === region) {
|
|
626
|
+
bucketsInRegion.push(bucket);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
// Skip buckets we can't access
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return bucketsInRegion.sort();
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
return [];
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* List GCS buckets in a specific region
|
|
642
|
+
*/
|
|
643
|
+
export async function listGcsBucketsInRegion(region) {
|
|
644
|
+
try {
|
|
645
|
+
// GCS locations can be multi-region (US, EU, ASIA) or single region
|
|
646
|
+
// We'll match on the region name (case-insensitive)
|
|
647
|
+
const result = await execCommand(`gcloud storage buckets list --format="json(name,location)"`);
|
|
648
|
+
if (result.stderr && !result.stdout) {
|
|
649
|
+
return [];
|
|
650
|
+
}
|
|
651
|
+
const buckets = JSON.parse(result.stdout);
|
|
652
|
+
return buckets
|
|
653
|
+
.filter((b) => b.location.toLowerCase() === region.toLowerCase())
|
|
654
|
+
.map((b) => b.name.replace("gs://", "").replace(/\/$/, ""))
|
|
655
|
+
.sort();
|
|
656
|
+
}
|
|
657
|
+
catch {
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* List Azure storage accounts in a specific region
|
|
663
|
+
*/
|
|
664
|
+
export async function listAzureStorageAccountsInRegion(region) {
|
|
665
|
+
try {
|
|
666
|
+
const result = await execCommand(`az storage account list --query "[?primaryLocation=='${region}'].name" --output json`);
|
|
667
|
+
if (result.stderr && !result.stdout) {
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
const accounts = JSON.parse(result.stdout);
|
|
671
|
+
return accounts.sort();
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
return [];
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* List buckets/storage for a specific provider in a specific region
|
|
679
|
+
*/
|
|
680
|
+
export async function listBucketsInRegion(provider, region) {
|
|
681
|
+
switch (provider) {
|
|
682
|
+
case "aws":
|
|
683
|
+
return listS3BucketsInRegion(region);
|
|
684
|
+
case "gcp":
|
|
685
|
+
return listGcsBucketsInRegion(region);
|
|
686
|
+
case "azure":
|
|
687
|
+
return listAzureStorageAccountsInRegion(region);
|
|
688
|
+
default:
|
|
689
|
+
return [];
|
|
690
|
+
}
|
|
691
|
+
}
|