@flui-cloud/cli 0.0.1 → 0.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.
Files changed (106) hide show
  1. package/lib/cli/src/commands/app/list.d.ts +3 -0
  2. package/lib/cli/src/commands/app/list.js +72 -18
  3. package/lib/cli/src/commands/app/status.d.ts +1 -0
  4. package/lib/cli/src/commands/app/status.js +27 -2
  5. package/lib/cli/src/commands/cluster/destroy.d.ts +1 -1
  6. package/lib/cli/src/commands/cluster/destroy.js +2 -2
  7. package/lib/cli/src/commands/deploy.d.ts +3 -0
  8. package/lib/cli/src/commands/deploy.js +19 -0
  9. package/lib/cli/src/commands/dev/creds.d.ts +0 -1
  10. package/lib/cli/src/commands/dev/creds.js +6 -27
  11. package/lib/cli/src/commands/dev/tunnel.js +8 -8
  12. package/lib/cli/src/commands/env/capacity.js +4 -4
  13. package/lib/cli/src/commands/env/create.d.ts +4 -1
  14. package/lib/cli/src/commands/env/create.js +73 -52
  15. package/lib/cli/src/commands/env/credentials.js +12 -12
  16. package/lib/cli/src/commands/env/destroy.d.ts +2 -1
  17. package/lib/cli/src/commands/env/destroy.js +45 -28
  18. package/lib/cli/src/commands/env/diag-ca.js +5 -5
  19. package/lib/cli/src/commands/env/export-config.d.ts +0 -17
  20. package/lib/cli/src/commands/env/export-config.js +45 -44
  21. package/lib/cli/src/commands/env/force-ready.d.ts +1 -1
  22. package/lib/cli/src/commands/env/force-ready.js +8 -8
  23. package/lib/cli/src/commands/env/inspect.js +5 -5
  24. package/lib/cli/src/commands/env/refresh-kubeconfig.js +4 -4
  25. package/lib/cli/src/commands/env/repair-ssh-ca.js +4 -4
  26. package/lib/cli/src/commands/env/repair-storage.d.ts +9 -0
  27. package/lib/cli/src/commands/env/repair-storage.js +82 -0
  28. package/lib/cli/src/commands/env/restart.d.ts +1 -1
  29. package/lib/cli/src/commands/env/restart.js +9 -9
  30. package/lib/cli/src/commands/env/scale-master.js +4 -4
  31. package/lib/cli/src/commands/env/scale-node.js +4 -4
  32. package/lib/cli/src/commands/env/set-master-protection.d.ts +16 -0
  33. package/lib/cli/src/commands/env/set-master-protection.js +120 -0
  34. package/lib/cli/src/commands/env/status.d.ts +1 -1
  35. package/lib/cli/src/commands/env/status.js +10 -10
  36. package/lib/cli/src/commands/env/stop.d.ts +1 -1
  37. package/lib/cli/src/commands/env/stop.js +8 -8
  38. package/lib/cli/src/commands/env/storage-expand.js +4 -4
  39. package/lib/cli/src/commands/env/storage.d.ts +1 -1
  40. package/lib/cli/src/commands/env/storage.js +5 -5
  41. package/lib/cli/src/commands/env/sync.js +5 -5
  42. package/lib/cli/src/commands/env/uncordon.js +4 -4
  43. package/lib/cli/src/commands/env/update-firewall.d.ts +13 -1
  44. package/lib/cli/src/commands/env/update-firewall.js +232 -126
  45. package/lib/cli/src/commands/integration/connect.d.ts +1 -0
  46. package/lib/cli/src/commands/integration/connect.js +19 -1
  47. package/lib/cli/src/commands/integration/reset.d.ts +13 -0
  48. package/lib/cli/src/commands/integration/reset.js +95 -0
  49. package/lib/cli/src/commands/integration/setup.d.ts +18 -0
  50. package/lib/cli/src/commands/integration/setup.js +320 -0
  51. package/lib/cli/src/commands/integration/status.d.ts +9 -0
  52. package/lib/cli/src/commands/integration/status.js +117 -0
  53. package/lib/cli/src/commands/node/list.d.ts +1 -0
  54. package/lib/cli/src/commands/node/list.js +19 -2
  55. package/lib/cli/src/commands/server-types/list.d.ts +3 -0
  56. package/lib/cli/src/commands/server-types/list.js +84 -0
  57. package/lib/cli/src/commands/ssh.js +5 -5
  58. package/lib/cli/src/commands/version.d.ts +18 -0
  59. package/lib/cli/src/commands/version.js +85 -0
  60. package/lib/cli/src/config/bootstrap.config.d.ts +10 -1
  61. package/lib/cli/src/config/bootstrap.config.js +21 -4
  62. package/lib/cli/src/config/preferences-schema.js +5 -5
  63. package/lib/cli/src/config/release.config.d.ts +31 -0
  64. package/lib/cli/src/config/release.config.js +38 -0
  65. package/lib/cli/src/lib/prompts.d.ts +1 -6
  66. package/lib/cli/src/lib/prompts.js +33 -13
  67. package/lib/cli/src/lib/services/cli-app.service.d.ts +33 -0
  68. package/lib/cli/src/lib/services/cli-app.service.js +9 -0
  69. package/lib/cli/src/lib/services/reconciliation.service.js +1 -1
  70. package/lib/cli/src/lib/templates/firewall-rules.d.ts +2 -2
  71. package/lib/cli/src/lib/templates/firewall-rules.js +3 -3
  72. package/lib/cli/src/modules/cli-infrastructure.module.js +3 -3
  73. package/lib/cli/src/services/cli-cluster-creator.service.js +31 -6
  74. package/lib/cli/src/services/cli-clusters.service.d.ts +3 -3
  75. package/lib/cli/src/services/cli-clusters.service.js +57 -34
  76. package/lib/cli/src/services/cli-control-cluster.service.d.ts +129 -0
  77. package/lib/cli/src/services/cli-control-cluster.service.js +544 -0
  78. package/lib/cli/src/services/cli-endpoint-resolver.service.d.ts +1 -0
  79. package/lib/cli/src/services/cli-endpoint-resolver.service.js +8 -2
  80. package/lib/cli/src/services/cli-k3s-script.service.d.ts +8 -1
  81. package/lib/cli/src/services/cli-k3s-script.service.js +14 -6
  82. package/lib/src/config/release.config.d.ts +28 -0
  83. package/lib/src/config/release.config.js +35 -0
  84. package/lib/src/modules/applications/entities/application.entity.d.ts +13 -20
  85. package/lib/src/modules/applications/entities/application.entity.js +12 -0
  86. package/lib/src/modules/applications/enums/application-exposure.enum.d.ts +2 -1
  87. package/lib/src/modules/applications/enums/application-exposure.enum.js +1 -0
  88. package/lib/src/modules/applications/interfaces/source-config.interface.d.ts +1 -0
  89. package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.d.ts +8 -2
  90. package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.js +16 -1
  91. package/lib/src/modules/infrastructure/clusters/services/cluster-node-scaling.service.js +2 -2
  92. package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.d.ts +3 -2
  93. package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.js +11 -4
  94. package/lib/src/modules/infrastructure/shared/services/kubernetes.service.d.ts +26 -0
  95. package/lib/src/modules/infrastructure/shared/services/kubernetes.service.js +105 -8
  96. package/lib/src/modules/management/entities/provider-capabilities.entity.d.ts +2 -0
  97. package/lib/src/modules/providers/implementations/contabo/contabo-capabilities.service.js +2 -0
  98. package/lib/src/modules/providers/implementations/hetzner/hetzner-capabilities.service.js +3 -6
  99. package/lib/src/modules/providers/implementations/scaleway/scaleway-capabilities.service.js +2 -1
  100. package/lib/src/modules/providers/implementations/scaleway/scaleway-firewall.service.js +3 -1
  101. package/lib/src/modules/providers/implementations/scaleway/scaleway-provider.service.js +3 -1
  102. package/lib/src/modules/providers/interfaces/provider-capabilities.interface.d.ts +0 -2
  103. package/lib/src/modules/providers/services/hetzner-firewall.service.d.ts +1 -1
  104. package/lib/src/modules/providers/services/hetzner-firewall.service.js +2 -1
  105. package/oclif.manifest.json +1025 -678
  106. package/package.json +2 -2
@@ -9,7 +9,7 @@ import { EncryptionService } from '../../../src/modules/shared/encryption/servic
9
9
  import { CliSshService } from './cli-ssh.service';
10
10
  import { CliCaService } from './cli-ca.service';
11
11
  import { ProviderFactory } from '../../../src/modules/providers/services/provider.factory';
12
- import { HetznerFirewallService } from '../../../src/modules/providers/services/hetzner-firewall.service';
12
+ import { FirewallProviderFactory } from '../../../src/modules/providers/core/factories/firewall-provider.factory';
13
13
  /**
14
14
  * CLI Clusters Service
15
15
  *
@@ -26,10 +26,10 @@ export declare class CliClustersService {
26
26
  private readonly sshService;
27
27
  private readonly caService;
28
28
  private readonly providerFactory;
29
- private readonly firewallService;
29
+ private readonly firewallFactory;
30
30
  private readonly infrastructureQueue;
31
31
  private readonly logger;
32
- constructor(clusterRepository: CliClusterRepository, operationRepository: CliOperationRepository, nodeRepository: CliNodeRepository, firewallRepository: CliFirewallRepository, encryptionService: EncryptionService, sshService: CliSshService, caService: CliCaService, providerFactory: ProviderFactory, firewallService: HetznerFirewallService, infrastructureQueue: any);
32
+ constructor(clusterRepository: CliClusterRepository, operationRepository: CliOperationRepository, nodeRepository: CliNodeRepository, firewallRepository: CliFirewallRepository, encryptionService: EncryptionService, sshService: CliSshService, caService: CliCaService, providerFactory: ProviderFactory, firewallFactory: FirewallProviderFactory, infrastructureQueue: any);
33
33
  /**
34
34
  * Create a new cluster
35
35
  */
@@ -63,7 +63,7 @@ const encryption_service_1 = require("../../../src/modules/shared/encryption/ser
63
63
  const cli_ssh_service_1 = require("./cli-ssh.service");
64
64
  const cli_ca_service_1 = require("./cli-ca.service");
65
65
  const provider_factory_1 = require("../../../src/modules/providers/services/provider.factory");
66
- const hetzner_firewall_service_1 = require("../../../src/modules/providers/services/hetzner-firewall.service");
66
+ const firewall_provider_factory_1 = require("../../../src/modules/providers/core/factories/firewall-provider.factory");
67
67
  const fs = __importStar(require("node:fs"));
68
68
  const path = __importStar(require("node:path"));
69
69
  const os = __importStar(require("node:os"));
@@ -75,7 +75,7 @@ const os = __importStar(require("node:os"));
75
75
  * Integrates SSH and CA management for secure server access.
76
76
  */
77
77
  let CliClustersService = CliClustersService_1 = class CliClustersService {
78
- constructor(clusterRepository, operationRepository, nodeRepository, firewallRepository, encryptionService, sshService, caService, providerFactory, firewallService, infrastructureQueue) {
78
+ constructor(clusterRepository, operationRepository, nodeRepository, firewallRepository, encryptionService, sshService, caService, providerFactory, firewallFactory, infrastructureQueue) {
79
79
  this.clusterRepository = clusterRepository;
80
80
  this.operationRepository = operationRepository;
81
81
  this.nodeRepository = nodeRepository;
@@ -84,7 +84,7 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
84
84
  this.sshService = sshService;
85
85
  this.caService = caService;
86
86
  this.providerFactory = providerFactory;
87
- this.firewallService = firewallService;
87
+ this.firewallFactory = firewallFactory;
88
88
  this.infrastructureQueue = infrastructureQueue;
89
89
  this.logger = new common_1.Logger(CliClustersService_1.name);
90
90
  }
@@ -134,17 +134,17 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
134
134
  const jwtSecret = this.generateSecurePassword(64);
135
135
  const adminEmail = createClusterDto.metadata?.adminEmail || '';
136
136
  const adminPassword = this.generateSecurePassword(24);
137
- // Generate Zitadel secrets (only used by OBSERVABILITY clusters)
137
+ // Generate Zitadel secrets (only used by control clusters)
138
138
  const zitadelMasterkey = this.generateSecurePassword(32);
139
139
  const zitadelDbAdminPassword = this.generateSecurePassword(32);
140
140
  const zitadelDbUserPassword = this.generateSecurePassword(32);
141
141
  const zitadelAdminTempPassword = this.generateSecurePassword(24);
142
142
  // Get worker count from DTO
143
143
  const workerCount = createClusterDto.workerCount;
144
- // Determine cluster type from metadata (same logic as API)
144
+ // Determine cluster type from metadata (same logic as API; accept legacy flag)
145
145
  const metadata = createClusterDto.metadata || {};
146
- const clusterType = metadata.isObservabilityCluster
147
- ? cluster_entity_1.ClusterType.OBSERVABILITY
146
+ const clusterType = metadata.isControlCluster || metadata.isObservabilityCluster
147
+ ? cluster_entity_1.ClusterType.CONTROL
148
148
  : cluster_entity_1.ClusterType.WORKLOAD;
149
149
  const hostnameMode = createClusterDto.endpointHostnameMode ?? hostname_mode_enum_1.HostnameMode.IP;
150
150
  let nipHostnameToken = null;
@@ -266,7 +266,7 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
266
266
  if (!cluster) {
267
267
  throw new Error(`Cluster ${id} not found`);
268
268
  }
269
- this.logger.log(`Deleting observability cluster ${id} (${cluster.name})`);
269
+ this.logger.log(`Deleting control cluster ${id} (${cluster.name})`);
270
270
  this.logger.log(`Cluster details: ${JSON.stringify({
271
271
  id: cluster.id,
272
272
  name: cluster.name,
@@ -367,14 +367,14 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
367
367
  labels: s.labels?.map((l) => `${l.key}=${l.value}`),
368
368
  })))}`);
369
369
  }
370
- // Filter servers that belong to this observability cluster
370
+ // Filter servers that belong to this control cluster
371
371
  const orphanedServers = allServers.filter((server) => {
372
372
  // Check if already deleted in Phase 1
373
373
  if (deletedServerIds.has(server.provider_resource_id)) {
374
374
  this.logger.log(`Skipping server ${server.name} - already deleted in Phase 1`);
375
375
  return false;
376
376
  }
377
- // Check if server belongs to this observability cluster
377
+ // Check if server belongs to this control cluster
378
378
  const hasObservabilityId = server.labels?.some((label) => label.key === 'flui-cluster-id' && label.value === cluster.id);
379
379
  // Check if server is managed by flui
380
380
  const isFluiManaged = server.labels?.some((label) => label.key === 'managed-by' && label.value === 'flui-cloud');
@@ -434,9 +434,11 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
434
434
  // PHASE 2.5: Delete cluster firewalls (GUARANTEED CLEANUP)
435
435
  this.logger.log(`Phase 2.5: Deleting cluster firewalls from ${cluster.provider}...`);
436
436
  try {
437
+ const providerEnum = (cluster.provider || '').toLowerCase();
438
+ const firewallService = this.firewallFactory.getFirewallProviderOrFail(providerEnum);
437
439
  // STEP 1: Find ALL firewalls for this cluster
438
440
  this.logger.log(`Searching for firewalls with label flui-cluster-id=${cluster.id}`);
439
- const clusterFirewalls = await this.firewallService.listFirewalls({
441
+ const clusterFirewalls = await firewallService.listFirewalls({
440
442
  clusterId: cluster.id,
441
443
  });
442
444
  this.logger.log(`Found ${clusterFirewalls.length} firewall(s) for cluster ${cluster.id}`);
@@ -444,13 +446,12 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
444
446
  let allFirewalls = [...clusterFirewalls];
445
447
  if (clusterFirewalls.length === 0) {
446
448
  this.logger.log(`No firewalls found by label. Searching by name pattern...`);
447
- // Search for firewalls with observability pattern:
448
- // - Old pattern: flui-observability-{clusterId}
449
- // - New pattern: flui-observability-firewall-{timestamp}
450
- const allProviderFirewalls = await this.firewallService.listFirewalls({});
451
- const nameMatchedFirewalls = allProviderFirewalls.filter((fw) => fw.name === `flui-observability-${cluster.id}` ||
452
- fw.name.startsWith('flui-observability-firewall-') ||
453
- fw.name.startsWith('flui-firewall-temp-'));
449
+ // Exact cluster-scoped names only never global prefixes (would hit
450
+ // another cluster's firewall).
451
+ const allProviderFirewalls = await firewallService.listFirewalls({});
452
+ const nameMatchedFirewalls = allProviderFirewalls.filter((fw) => fw.name === `flui-control-firewall-${cluster.id}` ||
453
+ fw.name === `flui-control-${cluster.id}` ||
454
+ fw.name === `flui-observability-${cluster.id}`);
454
455
  if (nameMatchedFirewalls.length > 0) {
455
456
  this.logger.log(`Found ${nameMatchedFirewalls.length} firewall(s) by name pattern:`);
456
457
  nameMatchedFirewalls.forEach((fw) => {
@@ -462,26 +463,45 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
462
463
  this.logger.log(`No firewalls found with observability name patterns`);
463
464
  }
464
465
  }
466
+ // STEP 1c: Sweep orphaned control firewalls left with a TEMPORARY name.
467
+ // A firewall created before a failed cluster run keeps its temp name
468
+ // (`flui-control-firewall` or `flui-control-firewall-<hex>`) and has no
469
+ // flui-cluster-id label, so neither the label query nor the scoped-name
470
+ // fallback above can find it. There is exactly one control cluster per
471
+ // environment, so any such firewall is leftover garbage from a previous
472
+ // attempt and is safe to remove while tearing down the control cluster.
473
+ // The regex only matches the temp form (8-hex suffix), never another
474
+ // cluster's UUID-scoped name.
475
+ if ((0, cluster_entity_1.isControlClusterType)(cluster.clusterType)) {
476
+ const tempControlFirewall = /^flui-control-firewall(-[0-9a-f]{8})?$/;
477
+ const known = new Set(allFirewalls.map((fw) => fw.id));
478
+ const providerFirewalls = await firewallService.listFirewalls({});
479
+ const orphans = providerFirewalls.filter((fw) => tempControlFirewall.test(fw.name) && !known.has(fw.id));
480
+ if (orphans.length > 0) {
481
+ this.logger.log(`Found ${orphans.length} orphaned control firewall(s) with a temporary name:`);
482
+ orphans.forEach((fw) => this.logger.log(` - ${fw.name} (${fw.id})`));
483
+ allFirewalls.push(...orphans);
484
+ }
485
+ }
465
486
  if (allFirewalls.length === 0) {
466
487
  this.logger.log('✓ No firewalls to delete for this cluster');
467
488
  }
468
489
  else {
469
- // STEP 2: Delete each firewall with retry logic for "resource_in_use" errors
490
+ // STEP 2: Delete each firewall, retrying while the server still holds it
470
491
  const deletePromises = allFirewalls.map(async (fw) => {
471
- const maxRetries = 5;
492
+ const maxRetries = 6;
472
493
  const retryDelay = 5000; // 5 seconds
473
494
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
474
495
  try {
475
496
  this.logger.log(`Deleting firewall ${fw.name} (${fw.id}) - Attempt ${attempt}/${maxRetries}`);
476
- await this.firewallService.deleteFirewall(fw.id);
497
+ await firewallService.deleteFirewall(fw.id);
477
498
  this.logger.log(`✓ Deleted firewall ${fw.name}`);
478
499
  return { success: true, id: fw.id, name: fw.name };
479
500
  }
480
501
  catch (error) {
481
- const isResourceInUse = error.message?.includes('resource_in_use') ||
482
- error.message?.includes('still in use');
483
- if (isResourceInUse && attempt < maxRetries) {
484
- this.logger.warn(`Firewall ${fw.name} is still in use. Waiting ${retryDelay}ms before retry ${attempt + 1}/${maxRetries}...`);
502
+ // Retry on any error: the firewall frees up once the server is gone.
503
+ if (attempt < maxRetries) {
504
+ this.logger.warn(`Firewall ${fw.name} not deletable yet (${error.message}); retry ${attempt + 1}/${maxRetries} in ${retryDelay}ms...`);
485
505
  await new Promise((resolve) => setTimeout(resolve, retryDelay));
486
506
  continue; // Retry
487
507
  }
@@ -500,7 +520,7 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
500
520
  const failedCount = results.filter((r) => !r.success).length;
501
521
  // STEP 3: VERIFICATION - Re-scan to ensure all deleted
502
522
  this.logger.log('Verifying all firewalls deleted...');
503
- const remainingFirewalls = await this.firewallService.listFirewalls({
523
+ const remainingFirewalls = await firewallService.listFirewalls({
504
524
  clusterId: cluster.id,
505
525
  });
506
526
  if (remainingFirewalls.length > 0) {
@@ -509,7 +529,7 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
509
529
  for (const fw of remainingFirewalls) {
510
530
  try {
511
531
  this.logger.log(`RETRY: Deleting firewall ${fw.name} (${fw.id})`);
512
- await this.firewallService.deleteFirewall(fw.id);
532
+ await firewallService.deleteFirewall(fw.id);
513
533
  this.logger.log(`✓ RETRY SUCCESS: Deleted firewall ${fw.name}`);
514
534
  }
515
535
  catch (error) {
@@ -517,7 +537,7 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
517
537
  }
518
538
  }
519
539
  // Final verification
520
- const finalCheck = await this.firewallService.listFirewalls({
540
+ const finalCheck = await firewallService.listFirewalls({
521
541
  clusterId: cluster.id,
522
542
  });
523
543
  if (finalCheck.length > 0) {
@@ -559,13 +579,16 @@ let CliClustersService = CliClustersService_1 = class CliClustersService {
559
579
  if (provider.listSSHKeys && provider.deleteSSHKey) {
560
580
  const allKeys = await provider.listSSHKeys();
561
581
  this.logger.log(`Found ${allKeys.length} total SSH keys on provider`);
562
- // Filter bootstrap keys for this observability cluster
582
+ // Filter bootstrap keys for this control cluster
563
583
  const bootstrapKeys = allKeys.filter((key) => {
564
584
  const tags = key.tags || {};
565
- const isFluiManaged = tags['managed-by'] === 'flui-cloud';
566
- const hasMatchingObsId = tags['flui-cluster-id'] === cluster.id;
567
- const isBootstrapKey = tags['flui-resource-type'] === 'ssh-key';
568
- return isFluiManaged && hasMatchingObsId && isBootstrapKey;
585
+ const tagMatch = tags['managed-by'] === 'flui-cloud' &&
586
+ tags['flui-cluster-id'] === cluster.id &&
587
+ tags['flui-resource-type'] === 'ssh-key';
588
+ // Scaleway IAM keys have no tags — match the cluster id in the name.
589
+ const nameMatch = typeof key.name === 'string' &&
590
+ key.name.startsWith(`flui-bootstrap-${cluster.id}`);
591
+ return tagMatch || nameMatch;
569
592
  });
570
593
  this.logger.log(`Found ${bootstrapKeys.length} bootstrap SSH keys to clean up`);
571
594
  // Delete bootstrap keys
@@ -758,5 +781,5 @@ exports.CliClustersService = CliClustersService = CliClustersService_1 = __decor
758
781
  cli_ssh_service_1.CliSshService,
759
782
  cli_ca_service_1.CliCaService,
760
783
  provider_factory_1.ProviderFactory,
761
- hetzner_firewall_service_1.HetznerFirewallService, Object])
784
+ firewall_provider_factory_1.FirewallProviderFactory, Object])
762
785
  ], CliClustersService);
@@ -0,0 +1,129 @@
1
+ import { ClusterEntity } from '../../../src/modules/infrastructure/clusters/entities/cluster.entity';
2
+ import { CliClusterRepository } from '../lib/repositories/cli-cluster.repository';
3
+ import { CliNodeRepository } from '../lib/repositories/cli-node.repository';
4
+ import { CliClustersService } from './cli-clusters.service';
5
+ import { CliOperationRepository } from '../lib/repositories/cli-operation.repository';
6
+ import { CliSshService } from './cli-ssh.service';
7
+ import { InfrastructureOperationEntity } from '../../../src/modules/infrastructure/servers/entities/infrastructure-operations.entity';
8
+ /**
9
+ * CLI Control Cluster Service
10
+ *
11
+ * Simplified version of ObservabilityClusterService for CLI usage.
12
+ * Uses file-based repositories instead of TypeORM.
13
+ */
14
+ export declare class CliControlClusterService {
15
+ private readonly clusterRepository;
16
+ private readonly nodeRepository;
17
+ private readonly clustersService;
18
+ private readonly operationRepository;
19
+ private readonly sshService;
20
+ private readonly logger;
21
+ constructor(clusterRepository: CliClusterRepository, nodeRepository: CliNodeRepository, clustersService: CliClustersService, operationRepository: CliOperationRepository, sshService: CliSshService);
22
+ /**
23
+ * Get the control cluster
24
+ */
25
+ getControlCluster(): Promise<ClusterEntity | null>;
26
+ /**
27
+ * Check if control cluster exists
28
+ */
29
+ hasControlCluster(): Promise<boolean>;
30
+ /**
31
+ * Get observability service endpoints
32
+ */
33
+ getObservabilityEndpoints(clusterId: string): Promise<{
34
+ prometheus?: string;
35
+ grafana?: string;
36
+ loki?: string;
37
+ postgres?: string;
38
+ redis?: string;
39
+ fluiApi?: string;
40
+ fluiWeb?: string;
41
+ }>;
42
+ /**
43
+ * Create control cluster
44
+ */
45
+ createControlCluster(provider: string, region: string, nodeSize: string, workerCount?: number, firewallId?: string, sourceCidrs?: string[], authMode?: string, envVnet?: {
46
+ vnetProviderResourceId: string;
47
+ vnetIpRange: string;
48
+ subnetProviderResourceId: string;
49
+ subnetIpRange: string;
50
+ subnetType: string;
51
+ networkZone: string;
52
+ }, adminEmail?: string, acmeStaging?: boolean, diskSizeGb?: number, options?: {
53
+ sharedStorageEnabled?: boolean;
54
+ sharedStorageVolumeSizeGb?: number;
55
+ useLatest?: boolean;
56
+ }): Promise<string>;
57
+ /**
58
+ * Deploy observability stack (Prometheus, Grafana, Loki)
59
+ */
60
+ deployObservabilityStack(clusterId: string): Promise<void>;
61
+ /**
62
+ * Delete control cluster
63
+ */
64
+ deleteControlCluster(): Promise<void>;
65
+ /**
66
+ * Get cluster operation by cluster ID
67
+ */
68
+ getClusterOperation(clusterId: string): Promise<InfrastructureOperationEntity | null>;
69
+ /**
70
+ * Wait for cluster to be ready by polling operation status
71
+ * @param clusterId Cluster ID to wait for
72
+ * @param timeoutMs Timeout in milliseconds (default: 10 minutes)
73
+ * @param pollIntervalMs Polling interval in milliseconds (default: 10 seconds)
74
+ * @returns Promise that resolves when cluster is ready
75
+ * @throws Error if timeout is reached or operation fails
76
+ */
77
+ waitForClusterReady(clusterId: string, timeoutMs?: number, pollIntervalMs?: number): Promise<void>;
78
+ /**
79
+ * Wait for master node IP address to become available
80
+ * Polls the cluster repository until masterIpAddress is populated
81
+ */
82
+ waitForMasterIp(clusterId: string, timeoutMs?: number, pollIntervalMs?: number): Promise<string>;
83
+ /**
84
+ * Wait for TCP port to become reachable on a host
85
+ */
86
+ waitForPortReady(host: string, port?: number, timeoutMs?: number, pollIntervalMs?: number): Promise<void>;
87
+ /**
88
+ * Wait for SSH to be fully ready (CA enrolled + cert auth working)
89
+ * Attempts an actual SSH command with retry until it succeeds.
90
+ * This is needed because TCP port 22 can be open before cloud-init
91
+ * has finished configuring the CA for certificate authentication.
92
+ *
93
+ * @param sshTestFn Function that attempts an SSH command and throws on failure
94
+ */
95
+ waitForSshAuth(sshTestFn: () => Promise<void>, timeoutMs?: number, pollIntervalMs?: number): Promise<void>;
96
+ /**
97
+ * Poll operation status in background and call onReady when COMPLETED.
98
+ * Returns a stop function and a done promise that resolves when the
99
+ * poller finishes (after onReady/onFailed callback completes or stop() is called).
100
+ */
101
+ pollOperationUntilReady(clusterId: string, onReady: () => Promise<void>, onFailed?: (error: string) => void, pollIntervalMs?: number): {
102
+ stop: () => void;
103
+ done: Promise<void>;
104
+ };
105
+ /**
106
+ * Check observability services health via HTTP endpoints
107
+ * @param masterIp Master node IP address
108
+ * @returns Object with service health status
109
+ */
110
+ checkObservabilityServices(masterIp: string, _nipHostnameToken?: string | null): Promise<{
111
+ prometheus: 'healthy' | 'unreachable';
112
+ grafana: 'healthy' | 'unreachable';
113
+ loki: 'healthy' | 'unreachable';
114
+ postgres: 'healthy' | 'unreachable';
115
+ redis: 'healthy' | 'unreachable';
116
+ fluiApi: 'healthy' | 'unreachable';
117
+ fluiWeb: 'healthy' | 'unreachable';
118
+ }>;
119
+ /**
120
+ * Check if a TCP port is reachable on a host
121
+ */
122
+ private checkTcpPort;
123
+ /**
124
+ * Sleep helper
125
+ */
126
+ private sleep;
127
+ waitForValidTls(url: string, timeoutMs?: number, intervalMs?: number, acmeStaging?: boolean): Promise<boolean>;
128
+ waitForOidcReady(apiBaseUrl: string, timeoutMs?: number, intervalMs?: number, acmeStaging?: boolean): Promise<boolean>;
129
+ }