@rulebricks/cli 2.0.5 → 2.1.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/dist/commands/deploy.js +50 -2
- package/dist/commands/destroy.js +1 -1
- package/dist/components/Wizard/steps/CloudProviderStep.js +18 -5
- package/dist/lib/cloudCli.d.ts +47 -3
- package/dist/lib/cloudCli.js +271 -53
- package/dist/lib/helmValues.js +50 -6
- package/dist/lib/terraform.d.ts +10 -3
- package/dist/lib/terraform.js +132 -5
- package/dist/types/index.js +87 -18
- package/package.json +1 -1
- package/terraform/aws/main.tf +5 -5
- package/terraform/azure/main.tf +4 -4
- package/terraform/gcp/main.tf +10 -6
package/dist/lib/cloudCli.js
CHANGED
|
@@ -128,27 +128,47 @@ export async function listS3Buckets() {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
|
-
* Static fallback for AWS regions
|
|
131
|
+
* Static fallback for AWS regions (c8g Graviton4 ARM64 available or expected)
|
|
132
132
|
*/
|
|
133
133
|
function getStaticAwsRegions() {
|
|
134
134
|
return [
|
|
135
|
+
// US regions (c8g Graviton4 available)
|
|
135
136
|
"us-east-1",
|
|
136
137
|
"us-east-2",
|
|
137
138
|
"us-west-1",
|
|
138
139
|
"us-west-2",
|
|
139
|
-
|
|
140
|
-
"ap-northeast-1",
|
|
141
|
-
"ap-northeast-2",
|
|
142
|
-
"ap-northeast-3",
|
|
143
|
-
"ap-southeast-1",
|
|
144
|
-
"ap-southeast-2",
|
|
140
|
+
// Canada
|
|
145
141
|
"ca-central-1",
|
|
146
|
-
"
|
|
142
|
+
"ca-west-1",
|
|
143
|
+
// Europe (c8g available)
|
|
147
144
|
"eu-west-1",
|
|
148
145
|
"eu-west-2",
|
|
149
146
|
"eu-west-3",
|
|
147
|
+
"eu-central-1",
|
|
148
|
+
"eu-central-2",
|
|
150
149
|
"eu-north-1",
|
|
150
|
+
"eu-south-1",
|
|
151
|
+
"eu-south-2",
|
|
152
|
+
// Asia Pacific (c8g available)
|
|
153
|
+
"ap-northeast-1",
|
|
154
|
+
"ap-northeast-2",
|
|
155
|
+
"ap-northeast-3",
|
|
156
|
+
"ap-southeast-1",
|
|
157
|
+
"ap-southeast-2",
|
|
158
|
+
"ap-southeast-3",
|
|
159
|
+
"ap-southeast-4",
|
|
160
|
+
"ap-southeast-5",
|
|
161
|
+
"ap-southeast-7",
|
|
162
|
+
"ap-south-1",
|
|
163
|
+
"ap-south-2",
|
|
164
|
+
"ap-east-1",
|
|
165
|
+
// South America
|
|
151
166
|
"sa-east-1",
|
|
167
|
+
// Middle East & Africa
|
|
168
|
+
"me-south-1",
|
|
169
|
+
"me-central-1",
|
|
170
|
+
"af-south-1",
|
|
171
|
+
"il-central-1",
|
|
152
172
|
];
|
|
153
173
|
}
|
|
154
174
|
/**
|
|
@@ -171,7 +191,12 @@ export async function listEksClusters(region) {
|
|
|
171
191
|
// GCP CLI (gcloud)
|
|
172
192
|
// ============================================================================
|
|
173
193
|
/**
|
|
174
|
-
* Check if gcloud CLI is installed and authenticated
|
|
194
|
+
* Check if gcloud CLI is installed and fully authenticated
|
|
195
|
+
*
|
|
196
|
+
* For GCP to be considered "authenticated", the user must have:
|
|
197
|
+
* 1. Logged in with `gcloud auth login`
|
|
198
|
+
* 2. Set a default project with `gcloud config set project PROJECT_ID`
|
|
199
|
+
* 3. Configured Application Default Credentials with `gcloud auth application-default login`
|
|
175
200
|
*/
|
|
176
201
|
export async function checkGcloudCli() {
|
|
177
202
|
const status = {
|
|
@@ -196,16 +221,27 @@ export async function checkGcloudCli() {
|
|
|
196
221
|
const config = JSON.parse(configResult.stdout);
|
|
197
222
|
const account = config.core?.account;
|
|
198
223
|
const project = config.core?.project;
|
|
224
|
+
// Step 1: Check if logged in
|
|
199
225
|
if (!account) {
|
|
200
226
|
status.error = 'Not authenticated - run "gcloud auth login"';
|
|
201
227
|
return status;
|
|
202
228
|
}
|
|
203
|
-
|
|
204
|
-
status.identity = project ? `Project: ${project}` : `Account: ${account}`;
|
|
229
|
+
// Step 2: Check if project is set
|
|
205
230
|
if (!project) {
|
|
206
231
|
status.error =
|
|
207
232
|
'No default project set - run "gcloud config set project PROJECT_ID"';
|
|
233
|
+
return status;
|
|
208
234
|
}
|
|
235
|
+
// Step 3: Check Application Default Credentials
|
|
236
|
+
const adcResult = await checkGcpApplicationDefaultCredentials();
|
|
237
|
+
if (!adcResult.configured) {
|
|
238
|
+
status.error =
|
|
239
|
+
'Application Default Credentials not configured - run "gcloud auth application-default login"';
|
|
240
|
+
return status;
|
|
241
|
+
}
|
|
242
|
+
// All checks passed
|
|
243
|
+
status.authenticated = true;
|
|
244
|
+
status.identity = `Project: ${project}`;
|
|
209
245
|
}
|
|
210
246
|
catch {
|
|
211
247
|
status.error = "Failed to parse gcloud config";
|
|
@@ -229,6 +265,39 @@ export async function getGcpProjectId() {
|
|
|
229
265
|
return null;
|
|
230
266
|
}
|
|
231
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Check if GCP Application Default Credentials (ADC) are configured
|
|
270
|
+
* ADC is required for Terraform to authenticate with Google Cloud
|
|
271
|
+
*/
|
|
272
|
+
export async function checkGcpApplicationDefaultCredentials() {
|
|
273
|
+
try {
|
|
274
|
+
// Try to get an access token using ADC
|
|
275
|
+
const result = await execCommand("gcloud auth application-default print-access-token");
|
|
276
|
+
if (result.stdout && result.stdout.trim().length > 0) {
|
|
277
|
+
return { configured: true };
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
configured: false,
|
|
281
|
+
error: "Application Default Credentials not configured",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
286
|
+
// Check if it's specifically an authentication error
|
|
287
|
+
if (errorMessage.includes("not found") ||
|
|
288
|
+
errorMessage.includes("not configured") ||
|
|
289
|
+
errorMessage.includes("Could not automatically determine credentials")) {
|
|
290
|
+
return {
|
|
291
|
+
configured: false,
|
|
292
|
+
error: "Application Default Credentials not configured",
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
configured: false,
|
|
297
|
+
error: errorMessage,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
232
301
|
/**
|
|
233
302
|
* List available GCP regions
|
|
234
303
|
*/
|
|
@@ -266,40 +335,33 @@ export async function listGcsBuckets() {
|
|
|
266
335
|
}
|
|
267
336
|
}
|
|
268
337
|
/**
|
|
269
|
-
* Static fallback for GCP regions
|
|
338
|
+
* Static fallback for GCP regions (C4A/Google Axion ARM64 confirmed availability)
|
|
339
|
+
* Only includes regions with full C4A zone coverage for GKE regional clusters
|
|
270
340
|
*/
|
|
271
341
|
function getStaticGcpRegions() {
|
|
272
342
|
return [
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
"us-
|
|
276
|
-
"us-
|
|
277
|
-
"us-
|
|
278
|
-
"us-
|
|
279
|
-
"us-west4",
|
|
280
|
-
|
|
281
|
-
"northamerica-
|
|
282
|
-
|
|
283
|
-
"
|
|
284
|
-
"europe-
|
|
285
|
-
"europe-
|
|
286
|
-
"europe-
|
|
287
|
-
"europe-
|
|
288
|
-
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"asia-
|
|
293
|
-
|
|
294
|
-
"
|
|
295
|
-
"asia-northeast2",
|
|
296
|
-
"asia-northeast3",
|
|
297
|
-
"asia-south1",
|
|
298
|
-
"asia-south2",
|
|
299
|
-
"asia-southeast1",
|
|
300
|
-
"asia-southeast2",
|
|
301
|
-
"australia-southeast1",
|
|
302
|
-
"australia-southeast2",
|
|
343
|
+
// Tier 1: Full C4A (Google Axion ARM64) availability - 3+ zones confirmed
|
|
344
|
+
// US regions
|
|
345
|
+
"us-central1", // C4A in zones a, b, c, f (best availability)
|
|
346
|
+
"us-east1", // C4A in zones b, c, d
|
|
347
|
+
"us-east4", // C4A in zones a, b, c
|
|
348
|
+
"us-west1", // C4A in zones a, b, c
|
|
349
|
+
"us-west4", // C4A in zones a, b, c
|
|
350
|
+
// North America
|
|
351
|
+
"northamerica-south1", // C4A in zones a, b, c (Mexico)
|
|
352
|
+
// Europe
|
|
353
|
+
"europe-west1", // C4A in zones b, c, d
|
|
354
|
+
"europe-west2", // C4A in zones a, b, c
|
|
355
|
+
"europe-west3", // C4A in zones a, b, c
|
|
356
|
+
"europe-west4", // C4A in zones a, b, c
|
|
357
|
+
"europe-north1", // C4A in zones a, b
|
|
358
|
+
// Asia Pacific
|
|
359
|
+
"asia-east1", // C4A in zones a, b, c
|
|
360
|
+
"asia-northeast1", // C4A in zones b, c
|
|
361
|
+
"asia-south1", // C4A in zones a, b, c
|
|
362
|
+
"asia-southeast1", // C4A in zones a, b, c
|
|
363
|
+
// Australia
|
|
364
|
+
"australia-southeast2", // C4A in zones a, b, c
|
|
303
365
|
];
|
|
304
366
|
}
|
|
305
367
|
/**
|
|
@@ -324,7 +386,13 @@ export async function listGkeClusters(region) {
|
|
|
324
386
|
// Azure CLI
|
|
325
387
|
// ============================================================================
|
|
326
388
|
/**
|
|
327
|
-
* Check if Azure CLI is installed and authenticated
|
|
389
|
+
* Check if Azure CLI is installed and fully authenticated
|
|
390
|
+
*
|
|
391
|
+
* For Azure to be considered "authenticated", the user must have:
|
|
392
|
+
* 1. Logged in with `az login`
|
|
393
|
+
* 2. An active subscription in "Enabled" state
|
|
394
|
+
* 3. Required resource providers registered (Microsoft.ContainerService, etc.)
|
|
395
|
+
* 4. Sufficient vCPU quota for at least the small tier (8 cores)
|
|
328
396
|
*/
|
|
329
397
|
export async function checkAzureCli() {
|
|
330
398
|
const status = {
|
|
@@ -343,22 +411,47 @@ export async function checkAzureCli() {
|
|
|
343
411
|
// Extract version (e.g., "azure-cli 2.51.0")
|
|
344
412
|
const versionMatch = versionResult.stdout.match(/azure-cli\s+([\d.]+)/);
|
|
345
413
|
status.version = versionMatch ? versionMatch[1] : undefined;
|
|
346
|
-
// Check authentication
|
|
414
|
+
// Step 1: Check authentication and subscription
|
|
347
415
|
const accountResult = await execCommand("az account show --output json");
|
|
348
416
|
if (accountResult.stderr && accountResult.stderr.includes("Please run")) {
|
|
349
417
|
status.error = 'Not authenticated - run "az login"';
|
|
350
418
|
return status;
|
|
351
419
|
}
|
|
420
|
+
let subscriptionName;
|
|
352
421
|
try {
|
|
353
422
|
const account = JSON.parse(accountResult.stdout);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
423
|
+
subscriptionName = account.name;
|
|
424
|
+
// Step 2: Check subscription state is Enabled
|
|
425
|
+
if (account.state !== "Enabled") {
|
|
426
|
+
status.error = `Subscription "${account.name}" is not enabled (state: ${account.state})`;
|
|
427
|
+
return status;
|
|
428
|
+
}
|
|
429
|
+
status.identity = subscriptionName
|
|
430
|
+
? `Subscription: ${subscriptionName}`
|
|
357
431
|
: undefined;
|
|
358
432
|
}
|
|
359
433
|
catch {
|
|
360
434
|
status.error = "Failed to parse account info";
|
|
435
|
+
return status;
|
|
436
|
+
}
|
|
437
|
+
// Step 3: Check required resource providers are registered
|
|
438
|
+
const providerCheck = await checkAzureResourceProviders();
|
|
439
|
+
if (!providerCheck.allRegistered) {
|
|
440
|
+
status.error =
|
|
441
|
+
`Resource providers not registered: ${providerCheck.missing.join(", ")}. ` +
|
|
442
|
+
`Run: ${providerCheck.missing.map((p) => `az provider register --namespace ${p}`).join(" && ")}`;
|
|
443
|
+
return status;
|
|
444
|
+
}
|
|
445
|
+
// Step 4: Check minimum vCPU quota (small tier = 8 cores)
|
|
446
|
+
const quotaCheck = await checkAzureVmQuota(AZURE_DEFAULT_QUOTA_CHECK_REGION, AZURE_TIER_CORES.small);
|
|
447
|
+
if (!quotaCheck.sufficient) {
|
|
448
|
+
status.error =
|
|
449
|
+
`Insufficient vCPU quota (${quotaCheck.available}/${quotaCheck.limit} available in ${AZURE_DEFAULT_QUOTA_CHECK_REGION}). ` +
|
|
450
|
+
"Request increase at Azure portal > Subscriptions > Usage + quotas";
|
|
451
|
+
return status;
|
|
361
452
|
}
|
|
453
|
+
// All checks passed
|
|
454
|
+
status.authenticated = true;
|
|
362
455
|
}
|
|
363
456
|
catch (error) {
|
|
364
457
|
status.error = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -426,42 +519,61 @@ export async function listAzureBlobContainers(storageAccount) {
|
|
|
426
519
|
}
|
|
427
520
|
}
|
|
428
521
|
/**
|
|
429
|
-
* Static fallback for Azure regions
|
|
522
|
+
* Static fallback for Azure regions (Dpsv5 ARM64 available or expected)
|
|
430
523
|
*/
|
|
431
524
|
function getStaticAzureRegions() {
|
|
432
525
|
return [
|
|
526
|
+
// US regions (Dpsv5 ARM64 available)
|
|
433
527
|
"eastus",
|
|
434
528
|
"eastus2",
|
|
435
|
-
"centralus",
|
|
436
|
-
"northcentralus",
|
|
437
|
-
"southcentralus",
|
|
438
529
|
"westus",
|
|
439
530
|
"westus2",
|
|
440
531
|
"westus3",
|
|
532
|
+
"centralus",
|
|
533
|
+
"northcentralus",
|
|
534
|
+
"southcentralus",
|
|
535
|
+
"westcentralus",
|
|
536
|
+
// Canada
|
|
441
537
|
"canadacentral",
|
|
442
538
|
"canadaeast",
|
|
539
|
+
// South America
|
|
443
540
|
"brazilsouth",
|
|
541
|
+
// Europe (Dpsv5 available)
|
|
444
542
|
"northeurope",
|
|
445
543
|
"westeurope",
|
|
446
544
|
"uksouth",
|
|
447
545
|
"ukwest",
|
|
448
546
|
"francecentral",
|
|
547
|
+
"francesouth",
|
|
449
548
|
"germanywestcentral",
|
|
549
|
+
"germanynorth",
|
|
450
550
|
"switzerlandnorth",
|
|
551
|
+
"switzerlandwest",
|
|
451
552
|
"norwayeast",
|
|
553
|
+
"norwaywest",
|
|
554
|
+
"swedencentral",
|
|
555
|
+
"polandcentral",
|
|
556
|
+
// Asia Pacific
|
|
452
557
|
"eastasia",
|
|
453
558
|
"southeastasia",
|
|
454
559
|
"japaneast",
|
|
455
560
|
"japanwest",
|
|
456
561
|
"koreacentral",
|
|
562
|
+
"koreasouth",
|
|
563
|
+
// Australia
|
|
457
564
|
"australiaeast",
|
|
458
565
|
"australiasoutheast",
|
|
459
566
|
"australiacentral",
|
|
567
|
+
// India
|
|
460
568
|
"centralindia",
|
|
461
569
|
"southindia",
|
|
462
570
|
"westindia",
|
|
571
|
+
// Middle East & Africa
|
|
463
572
|
"uaenorth",
|
|
573
|
+
"uaecentral",
|
|
464
574
|
"southafricanorth",
|
|
575
|
+
"qatarcentral",
|
|
576
|
+
"israelcentral",
|
|
465
577
|
];
|
|
466
578
|
}
|
|
467
579
|
/**
|
|
@@ -481,6 +593,104 @@ export async function listAksClusters(resourceGroup) {
|
|
|
481
593
|
return [];
|
|
482
594
|
}
|
|
483
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Required Azure resource providers for AKS deployment
|
|
598
|
+
*/
|
|
599
|
+
const AZURE_REQUIRED_PROVIDERS = [
|
|
600
|
+
"Microsoft.ContainerService", // For AKS
|
|
601
|
+
"Microsoft.Network", // For VNets, NSGs
|
|
602
|
+
"Microsoft.ManagedIdentity", // For managed identities
|
|
603
|
+
"Microsoft.Compute", // For VMs
|
|
604
|
+
];
|
|
605
|
+
/**
|
|
606
|
+
* Azure tier to vCPU core requirements mapping
|
|
607
|
+
*/
|
|
608
|
+
export const AZURE_TIER_CORES = {
|
|
609
|
+
small: 8, // 4 nodes × 2 vCPU
|
|
610
|
+
medium: 16, // 4 nodes × 4 vCPU
|
|
611
|
+
large: 40, // 5 nodes × 8 vCPU
|
|
612
|
+
};
|
|
613
|
+
/**
|
|
614
|
+
* Default region used for baseline quota checks when region is not yet selected
|
|
615
|
+
*/
|
|
616
|
+
const AZURE_DEFAULT_QUOTA_CHECK_REGION = "eastus";
|
|
617
|
+
/**
|
|
618
|
+
* Check if required Azure resource providers are registered
|
|
619
|
+
*/
|
|
620
|
+
export async function checkAzureResourceProviders() {
|
|
621
|
+
const missing = [];
|
|
622
|
+
try {
|
|
623
|
+
for (const provider of AZURE_REQUIRED_PROVIDERS) {
|
|
624
|
+
const result = await execCommand(`az provider show --namespace ${provider} --query "registrationState" --output tsv`);
|
|
625
|
+
const state = result.stdout.trim();
|
|
626
|
+
if (state !== "Registered") {
|
|
627
|
+
missing.push(provider);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
allRegistered: missing.length === 0,
|
|
632
|
+
missing,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
// If we can't check, assume they're not registered
|
|
637
|
+
return {
|
|
638
|
+
allRegistered: false,
|
|
639
|
+
missing: AZURE_REQUIRED_PROVIDERS,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Check Azure VM quota for a specific region
|
|
645
|
+
*
|
|
646
|
+
* @param region - Azure region to check quota for
|
|
647
|
+
* @param requiredCores - Number of vCPUs required
|
|
648
|
+
* @returns Quota check result with availability info
|
|
649
|
+
*/
|
|
650
|
+
export async function checkAzureVmQuota(region, requiredCores) {
|
|
651
|
+
try {
|
|
652
|
+
const result = await execCommand(`az vm list-usage --location ${region} --output json`);
|
|
653
|
+
if (result.stderr && !result.stdout) {
|
|
654
|
+
return {
|
|
655
|
+
sufficient: false,
|
|
656
|
+
available: 0,
|
|
657
|
+
limit: 0,
|
|
658
|
+
used: 0,
|
|
659
|
+
error: "Failed to check VM quota",
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
const usageList = JSON.parse(result.stdout);
|
|
663
|
+
// Find total regional vCPU quota
|
|
664
|
+
const regionalQuota = usageList.find((u) => u.name.value === "cores" || u.name.localizedValue === "Total Regional vCPUs");
|
|
665
|
+
if (!regionalQuota) {
|
|
666
|
+
return {
|
|
667
|
+
sufficient: false,
|
|
668
|
+
available: 0,
|
|
669
|
+
limit: 0,
|
|
670
|
+
used: 0,
|
|
671
|
+
error: "Could not find regional vCPU quota",
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
const used = regionalQuota.currentValue;
|
|
675
|
+
const limit = regionalQuota.limit;
|
|
676
|
+
const available = limit - used;
|
|
677
|
+
return {
|
|
678
|
+
sufficient: available >= requiredCores,
|
|
679
|
+
available,
|
|
680
|
+
limit,
|
|
681
|
+
used,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
return {
|
|
686
|
+
sufficient: false,
|
|
687
|
+
available: 0,
|
|
688
|
+
limit: 0,
|
|
689
|
+
used: 0,
|
|
690
|
+
error: error instanceof Error ? error.message : "Failed to check VM quota",
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
}
|
|
484
694
|
// ============================================================================
|
|
485
695
|
// Aggregated Functions
|
|
486
696
|
// ============================================================================
|
|
@@ -567,8 +777,16 @@ export const CLI_INSTALL_URLS = {
|
|
|
567
777
|
*/
|
|
568
778
|
export const CLI_LOGIN_COMMANDS = {
|
|
569
779
|
aws: "aws configure",
|
|
570
|
-
gcp:
|
|
571
|
-
|
|
780
|
+
gcp: [
|
|
781
|
+
"gcloud auth login",
|
|
782
|
+
"gcloud config set project PROJECT_ID",
|
|
783
|
+
"gcloud auth application-default login",
|
|
784
|
+
],
|
|
785
|
+
azure: [
|
|
786
|
+
"az login",
|
|
787
|
+
"az account set --subscription YOUR_SUBSCRIPTION_ID",
|
|
788
|
+
"az provider register --namespace Microsoft.ContainerService",
|
|
789
|
+
],
|
|
572
790
|
};
|
|
573
791
|
/**
|
|
574
792
|
* Check if Terraform is installed
|
package/dist/lib/helmValues.js
CHANGED
|
@@ -208,13 +208,24 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
208
208
|
// Determine if external-dns should be enabled
|
|
209
209
|
const externalDnsEnabled = config.dns.autoManage && isSupportedDnsProvider(config.dns.provider);
|
|
210
210
|
// Determine storage class based on provider
|
|
211
|
+
// Note: GCP uses "hyperdisk-balanced" because C4A instances only support Hyperdisk (not Persistent Disk)
|
|
211
212
|
const storageClass = config.infrastructure.provider === "aws"
|
|
212
213
|
? "gp3"
|
|
213
214
|
: config.infrastructure.provider === "gcp"
|
|
214
|
-
? "
|
|
215
|
+
? "hyperdisk-balanced"
|
|
215
216
|
: config.infrastructure.provider === "azure"
|
|
216
217
|
? "managed-premium"
|
|
217
218
|
: "gp3";
|
|
219
|
+
// ARM64 tolerations for GKE C4A nodes (and other ARM64 providers)
|
|
220
|
+
// GKE automatically taints ARM64 nodes with kubernetes.io/arch=arm64:NoSchedule
|
|
221
|
+
const arm64Tolerations = [
|
|
222
|
+
{
|
|
223
|
+
key: "kubernetes.io/arch",
|
|
224
|
+
operator: "Equal",
|
|
225
|
+
value: "arm64",
|
|
226
|
+
effect: "NoSchedule",
|
|
227
|
+
},
|
|
228
|
+
];
|
|
218
229
|
// Build global.supabase configuration
|
|
219
230
|
const supabaseGlobalConfig = config.database.type === "supabase-cloud"
|
|
220
231
|
? {
|
|
@@ -305,6 +316,7 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
305
316
|
: {}),
|
|
306
317
|
replicaCount: tierConfig.appReplicas,
|
|
307
318
|
resources: tierConfig.appResources,
|
|
319
|
+
tolerations: arm64Tolerations,
|
|
308
320
|
// Logging configuration
|
|
309
321
|
logging: {
|
|
310
322
|
enabled: true,
|
|
@@ -327,6 +339,7 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
327
339
|
: {}),
|
|
328
340
|
replicas: tierConfig.hpsReplicas,
|
|
329
341
|
resources: tierConfig.hpsResources,
|
|
342
|
+
tolerations: arm64Tolerations,
|
|
330
343
|
// HPS Workers with KEDA autoscaling
|
|
331
344
|
workers: {
|
|
332
345
|
enabled: true,
|
|
@@ -341,6 +354,7 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
341
354
|
cpuThreshold: 25,
|
|
342
355
|
},
|
|
343
356
|
resources: tierConfig.hpsWorkerResources,
|
|
357
|
+
tolerations: arm64Tolerations,
|
|
344
358
|
},
|
|
345
359
|
},
|
|
346
360
|
// Ingress configuration
|
|
@@ -352,6 +366,7 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
352
366
|
// Redis configuration
|
|
353
367
|
redis: {
|
|
354
368
|
resources: tierConfig.redisResources,
|
|
369
|
+
tolerations: arm64Tolerations,
|
|
355
370
|
persistence: {
|
|
356
371
|
enabled: true,
|
|
357
372
|
size: tierConfig.redisPersistenceSize,
|
|
@@ -382,6 +397,7 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
382
397
|
controller: {
|
|
383
398
|
replicaCount: tierConfig.kafkaReplication,
|
|
384
399
|
resources: tierConfig.kafkaResources,
|
|
400
|
+
tolerations: arm64Tolerations,
|
|
385
401
|
persistence: {
|
|
386
402
|
enabled: true,
|
|
387
403
|
size: tierConfig.kafkaStorage,
|
|
@@ -410,6 +426,7 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
410
426
|
ingressClass: {
|
|
411
427
|
name: "traefik",
|
|
412
428
|
},
|
|
429
|
+
tolerations: arm64Tolerations,
|
|
413
430
|
autoscaling: {
|
|
414
431
|
enabled: true,
|
|
415
432
|
minReplicas: 1,
|
|
@@ -450,6 +467,7 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
450
467
|
// =============================================================================
|
|
451
468
|
keda: {
|
|
452
469
|
enabled: true,
|
|
470
|
+
tolerations: arm64Tolerations,
|
|
453
471
|
crds: {
|
|
454
472
|
install: false, // CRDs managed in parent chart
|
|
455
473
|
},
|
|
@@ -460,6 +478,13 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
460
478
|
"cert-manager": {
|
|
461
479
|
enabled: tlsEnabled,
|
|
462
480
|
installCRDs: false, // CRDs managed in parent chart
|
|
481
|
+
tolerations: arm64Tolerations,
|
|
482
|
+
webhook: {
|
|
483
|
+
tolerations: arm64Tolerations,
|
|
484
|
+
},
|
|
485
|
+
cainjector: {
|
|
486
|
+
tolerations: arm64Tolerations,
|
|
487
|
+
},
|
|
463
488
|
},
|
|
464
489
|
// Cluster Issuer for Let's Encrypt
|
|
465
490
|
clusterIssuer: {
|
|
@@ -475,6 +500,7 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
475
500
|
role: "Stateless-Aggregator",
|
|
476
501
|
replicas: tierConfig.vectorReplicas,
|
|
477
502
|
resources: tierConfig.vectorResources,
|
|
503
|
+
tolerations: arm64Tolerations,
|
|
478
504
|
service: {
|
|
479
505
|
enabled: true,
|
|
480
506
|
ports: [{ name: "api", port: 8686, protocol: "TCP", targetPort: 8686 }],
|
|
@@ -527,19 +553,36 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
527
553
|
},
|
|
528
554
|
db: {
|
|
529
555
|
resources: tierConfig.dbResources,
|
|
556
|
+
tolerations: arm64Tolerations,
|
|
530
557
|
persistence: {
|
|
531
558
|
enabled: true,
|
|
532
559
|
size: tierConfig.dbPersistenceSize,
|
|
533
560
|
storageClassName: storageClass,
|
|
534
561
|
},
|
|
535
562
|
},
|
|
563
|
+
auth: {
|
|
564
|
+
tolerations: arm64Tolerations,
|
|
565
|
+
},
|
|
566
|
+
rest: {
|
|
567
|
+
tolerations: arm64Tolerations,
|
|
568
|
+
},
|
|
569
|
+
realtime: {
|
|
570
|
+
tolerations: arm64Tolerations,
|
|
571
|
+
},
|
|
572
|
+
meta: {
|
|
573
|
+
tolerations: arm64Tolerations,
|
|
574
|
+
},
|
|
536
575
|
kong: {
|
|
576
|
+
tolerations: arm64Tolerations,
|
|
537
577
|
ingress: {
|
|
538
578
|
enabled: true,
|
|
539
579
|
className: "traefik",
|
|
540
580
|
annotations: {},
|
|
541
581
|
},
|
|
542
582
|
},
|
|
583
|
+
studio: {
|
|
584
|
+
tolerations: arm64Tolerations,
|
|
585
|
+
},
|
|
543
586
|
}
|
|
544
587
|
: {}),
|
|
545
588
|
},
|
|
@@ -597,13 +640,14 @@ export async function generateHelmValues(config, options = {}) {
|
|
|
597
640
|
: config.infrastructure.provider === "azure"
|
|
598
641
|
? "disk.csi.azure.com"
|
|
599
642
|
: "ebs.csi.aws.com",
|
|
600
|
-
type
|
|
601
|
-
|
|
643
|
+
// Parameters for the StorageClass - must include type for disk provisioning
|
|
644
|
+
parameters: config.infrastructure.provider === "aws"
|
|
645
|
+
? { type: "gp3" }
|
|
602
646
|
: config.infrastructure.provider === "gcp"
|
|
603
|
-
? "
|
|
647
|
+
? { type: "hyperdisk-balanced" }
|
|
604
648
|
: config.infrastructure.provider === "azure"
|
|
605
|
-
? "Premium_LRS"
|
|
606
|
-
: "gp3",
|
|
649
|
+
? { skuName: "Premium_LRS" }
|
|
650
|
+
: { type: "gp3" },
|
|
607
651
|
fsType: "ext4",
|
|
608
652
|
reclaimPolicy: "Delete",
|
|
609
653
|
volumeBindingMode: "WaitForFirstConsumer",
|
package/dist/lib/terraform.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CloudProvider } from '../types/index.js';
|
|
1
|
+
import { CloudProvider, DeploymentConfig } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Checks if Terraform is installed
|
|
4
4
|
*/
|
|
@@ -24,7 +24,8 @@ export declare function terraformPlan(deploymentName: string): Promise<void>;
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function terraformApply(deploymentName: string): Promise<void>;
|
|
26
26
|
/**
|
|
27
|
-
* Destroys Terraform infrastructure
|
|
27
|
+
* Destroys Terraform infrastructure.
|
|
28
|
+
* Runs init first to ensure .terraform folder exists (handles partial deployments).
|
|
28
29
|
*/
|
|
29
30
|
export declare function terraformDestroy(deploymentName: string): Promise<void>;
|
|
30
31
|
/**
|
|
@@ -32,9 +33,15 @@ export declare function terraformDestroy(deploymentName: string): Promise<void>;
|
|
|
32
33
|
*/
|
|
33
34
|
export declare function getTerraformOutputs(deploymentName: string): Promise<Record<string, string>>;
|
|
34
35
|
/**
|
|
35
|
-
* Checks if Terraform state
|
|
36
|
+
* Checks if Terraform files/state exist for a deployment.
|
|
37
|
+
* Returns true if the terraform directory contains any terraform files,
|
|
38
|
+
* not just the state file. This allows destroy to work on partial infrastructure.
|
|
36
39
|
*/
|
|
37
40
|
export declare function hasTerraformState(deploymentName: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Generates Terraform variables from deployment configuration
|
|
43
|
+
*/
|
|
44
|
+
export declare function generateTerraformVars(config: DeploymentConfig): Record<string, unknown>;
|
|
38
45
|
/**
|
|
39
46
|
* Updates kubeconfig for the provisioned cluster
|
|
40
47
|
*/
|