@flui-cloud/cli 0.0.1 → 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/lib/cli/src/commands/app/list.d.ts +3 -0
- package/lib/cli/src/commands/app/list.js +72 -18
- package/lib/cli/src/commands/app/status.d.ts +1 -0
- package/lib/cli/src/commands/app/status.js +27 -2
- package/lib/cli/src/commands/cluster/destroy.d.ts +1 -1
- package/lib/cli/src/commands/cluster/destroy.js +2 -2
- package/lib/cli/src/commands/deploy.d.ts +3 -0
- package/lib/cli/src/commands/deploy.js +19 -0
- package/lib/cli/src/commands/dev/creds.d.ts +0 -1
- package/lib/cli/src/commands/dev/creds.js +6 -27
- package/lib/cli/src/commands/dev/tunnel.js +8 -8
- package/lib/cli/src/commands/env/capacity.js +4 -4
- package/lib/cli/src/commands/env/create.d.ts +4 -1
- package/lib/cli/src/commands/env/create.js +78 -52
- package/lib/cli/src/commands/env/credentials.js +12 -12
- package/lib/cli/src/commands/env/destroy.d.ts +2 -1
- package/lib/cli/src/commands/env/destroy.js +45 -28
- package/lib/cli/src/commands/env/diag-ca.js +5 -5
- package/lib/cli/src/commands/env/export-config.d.ts +0 -17
- package/lib/cli/src/commands/env/export-config.js +50 -47
- package/lib/cli/src/commands/env/force-ready.d.ts +1 -1
- package/lib/cli/src/commands/env/force-ready.js +8 -8
- package/lib/cli/src/commands/env/inspect.js +5 -5
- package/lib/cli/src/commands/env/refresh-kubeconfig.js +4 -4
- package/lib/cli/src/commands/env/repair-ssh-ca.js +4 -4
- package/lib/cli/src/commands/env/repair-storage.d.ts +9 -0
- package/lib/cli/src/commands/env/repair-storage.js +82 -0
- package/lib/cli/src/commands/env/restart.d.ts +1 -1
- package/lib/cli/src/commands/env/restart.js +9 -9
- package/lib/cli/src/commands/env/scale-master.js +4 -4
- package/lib/cli/src/commands/env/scale-node.js +4 -4
- package/lib/cli/src/commands/env/set-master-protection.d.ts +16 -0
- package/lib/cli/src/commands/env/set-master-protection.js +120 -0
- package/lib/cli/src/commands/env/status.d.ts +1 -1
- package/lib/cli/src/commands/env/status.js +10 -10
- package/lib/cli/src/commands/env/stop.d.ts +1 -1
- package/lib/cli/src/commands/env/stop.js +8 -8
- package/lib/cli/src/commands/env/storage-expand.js +4 -4
- package/lib/cli/src/commands/env/storage.d.ts +1 -1
- package/lib/cli/src/commands/env/storage.js +5 -5
- package/lib/cli/src/commands/env/sync.js +5 -5
- package/lib/cli/src/commands/env/uncordon.js +4 -4
- package/lib/cli/src/commands/env/update-firewall.d.ts +13 -1
- package/lib/cli/src/commands/env/update-firewall.js +232 -126
- package/lib/cli/src/commands/integration/connect.d.ts +1 -0
- package/lib/cli/src/commands/integration/connect.js +19 -1
- package/lib/cli/src/commands/integration/reset.d.ts +13 -0
- package/lib/cli/src/commands/integration/reset.js +95 -0
- package/lib/cli/src/commands/integration/setup.d.ts +18 -0
- package/lib/cli/src/commands/integration/setup.js +320 -0
- package/lib/cli/src/commands/integration/status.d.ts +9 -0
- package/lib/cli/src/commands/integration/status.js +117 -0
- package/lib/cli/src/commands/node/list.d.ts +1 -0
- package/lib/cli/src/commands/node/list.js +19 -2
- package/lib/cli/src/commands/server-types/list.d.ts +3 -0
- package/lib/cli/src/commands/server-types/list.js +84 -0
- package/lib/cli/src/commands/ssh.js +5 -5
- package/lib/cli/src/commands/version.d.ts +18 -0
- package/lib/cli/src/commands/version.js +100 -0
- package/lib/cli/src/config/bootstrap.config.d.ts +10 -1
- package/lib/cli/src/config/bootstrap.config.js +24 -4
- package/lib/cli/src/config/preferences-schema.js +5 -5
- package/lib/cli/src/config/release-override.d.ts +43 -0
- package/lib/cli/src/config/release-override.js +203 -0
- package/lib/cli/src/config/release.config.d.ts +31 -0
- package/lib/cli/src/config/release.config.js +38 -0
- package/lib/cli/src/lib/prompts.d.ts +1 -6
- package/lib/cli/src/lib/prompts.js +33 -13
- package/lib/cli/src/lib/services/cli-app.service.d.ts +33 -0
- package/lib/cli/src/lib/services/cli-app.service.js +9 -0
- package/lib/cli/src/lib/services/reconciliation.service.js +1 -1
- package/lib/cli/src/lib/templates/firewall-rules.d.ts +2 -2
- package/lib/cli/src/lib/templates/firewall-rules.js +3 -3
- package/lib/cli/src/modules/cli-infrastructure.module.js +3 -3
- package/lib/cli/src/services/cli-cluster-creator.service.js +31 -6
- package/lib/cli/src/services/cli-clusters.service.d.ts +3 -3
- package/lib/cli/src/services/cli-clusters.service.js +57 -34
- package/lib/cli/src/services/cli-control-cluster.service.d.ts +129 -0
- package/lib/cli/src/services/cli-control-cluster.service.js +545 -0
- package/lib/cli/src/services/cli-endpoint-resolver.service.d.ts +1 -0
- package/lib/cli/src/services/cli-endpoint-resolver.service.js +25 -11
- package/lib/cli/src/services/cli-k3s-script.service.d.ts +8 -1
- package/lib/cli/src/services/cli-k3s-script.service.js +14 -6
- package/lib/src/config/release.config.d.ts +28 -0
- package/lib/src/config/release.config.js +35 -0
- package/lib/src/modules/applications/entities/application.entity.d.ts +13 -20
- package/lib/src/modules/applications/entities/application.entity.js +12 -0
- package/lib/src/modules/applications/enums/application-exposure.enum.d.ts +2 -1
- package/lib/src/modules/applications/enums/application-exposure.enum.js +1 -0
- package/lib/src/modules/applications/interfaces/source-config.interface.d.ts +1 -0
- package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.d.ts +8 -2
- package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.js +16 -1
- package/lib/src/modules/infrastructure/clusters/services/cluster-node-scaling.service.js +2 -2
- package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.d.ts +3 -2
- package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.js +11 -4
- package/lib/src/modules/infrastructure/shared/services/kubernetes.service.d.ts +26 -0
- package/lib/src/modules/infrastructure/shared/services/kubernetes.service.js +105 -8
- package/lib/src/modules/management/entities/provider-capabilities.entity.d.ts +2 -0
- package/lib/src/modules/providers/implementations/contabo/contabo-capabilities.service.js +2 -0
- package/lib/src/modules/providers/implementations/hetzner/hetzner-capabilities.service.js +3 -6
- package/lib/src/modules/providers/implementations/scaleway/scaleway-capabilities.service.js +2 -1
- package/lib/src/modules/providers/implementations/scaleway/scaleway-firewall.service.js +3 -1
- package/lib/src/modules/providers/implementations/scaleway/scaleway-provider.service.js +3 -1
- package/lib/src/modules/providers/interfaces/provider-capabilities.interface.d.ts +0 -2
- package/lib/src/modules/providers/services/hetzner-firewall.service.d.ts +1 -1
- package/lib/src/modules/providers/services/hetzner-firewall.service.js +2 -1
- package/oclif.manifest.json +1201 -854
- package/package.json +2 -2
|
@@ -10,6 +10,24 @@ export interface AppSummary {
|
|
|
10
10
|
lastDeployedAt?: string;
|
|
11
11
|
clusterId: string;
|
|
12
12
|
}
|
|
13
|
+
export interface AppGroupComponent extends AppSummary {
|
|
14
|
+
isPrimary?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface AppGroup {
|
|
17
|
+
id: string;
|
|
18
|
+
type: 'standalone' | 'composed';
|
|
19
|
+
name: string;
|
|
20
|
+
slug: string;
|
|
21
|
+
status: string;
|
|
22
|
+
category: string;
|
|
23
|
+
clusterId: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
catalogSlug?: string;
|
|
26
|
+
catalogInstallId?: string;
|
|
27
|
+
primaryComponentId?: string;
|
|
28
|
+
componentCount: number;
|
|
29
|
+
components: AppGroupComponent[];
|
|
30
|
+
}
|
|
13
31
|
export interface AppRuntime {
|
|
14
32
|
appId: string;
|
|
15
33
|
deploymentName: string;
|
|
@@ -186,14 +204,29 @@ export interface AvailableVersionsResponse {
|
|
|
186
204
|
nextPage: number | null;
|
|
187
205
|
allowedPatterns: string[] | null;
|
|
188
206
|
}
|
|
207
|
+
export interface AppEndpoint {
|
|
208
|
+
id: string;
|
|
209
|
+
clusterId: string;
|
|
210
|
+
applicationId?: string;
|
|
211
|
+
endpointType: string;
|
|
212
|
+
hostnameMode: string;
|
|
213
|
+
fqdn: string;
|
|
214
|
+
tlsEnabled: boolean;
|
|
215
|
+
certificateStatus?: string;
|
|
216
|
+
certificateMessage?: string;
|
|
217
|
+
reconciliationStatus?: string;
|
|
218
|
+
errorMessage?: string;
|
|
219
|
+
}
|
|
189
220
|
export declare class CliAppService {
|
|
190
221
|
private readonly apiClient;
|
|
191
222
|
private readonly clusterId;
|
|
192
223
|
constructor(apiClient: ApiClient, clusterId: string);
|
|
193
224
|
static create(clusterId: string): Promise<CliAppService>;
|
|
194
225
|
listApps(): Promise<AppSummary[]>;
|
|
226
|
+
listAppGroups(): Promise<AppGroup[]>;
|
|
195
227
|
getAppByName(name: string): Promise<AppSummary>;
|
|
196
228
|
getRuntime(appId: string): Promise<AppRuntime>;
|
|
229
|
+
listEndpoints(applicationId?: string): Promise<AppEndpoint[]>;
|
|
197
230
|
getLogs(options: AppLogsOptions): Promise<AppLogsResponse>;
|
|
198
231
|
scale(appId: string, replicas: number): Promise<AppRuntime>;
|
|
199
232
|
restart(appId: string): Promise<void>;
|
|
@@ -21,6 +21,9 @@ class CliAppService {
|
|
|
21
21
|
async listApps() {
|
|
22
22
|
return this.apiClient.get(`/clusters/${this.clusterId}/applications`);
|
|
23
23
|
}
|
|
24
|
+
async listAppGroups() {
|
|
25
|
+
return this.apiClient.get(`/clusters/${this.clusterId}/applications/grouped`);
|
|
26
|
+
}
|
|
24
27
|
async getAppByName(name) {
|
|
25
28
|
const apps = await this.listApps();
|
|
26
29
|
const app = apps.find((a) => a.name.toLowerCase() === name.toLowerCase() ||
|
|
@@ -33,6 +36,12 @@ class CliAppService {
|
|
|
33
36
|
async getRuntime(appId) {
|
|
34
37
|
return this.apiClient.get(`/applications/${appId}/runtime`);
|
|
35
38
|
}
|
|
39
|
+
async listEndpoints(applicationId) {
|
|
40
|
+
const all = await this.apiClient.get(`/clusters/${this.clusterId}/endpoints`);
|
|
41
|
+
return applicationId
|
|
42
|
+
? all.filter((e) => e.applicationId === applicationId)
|
|
43
|
+
: all;
|
|
44
|
+
}
|
|
36
45
|
async getLogs(options) {
|
|
37
46
|
const params = new URLSearchParams();
|
|
38
47
|
if (options.app)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @deprecated Import from shared template instead:
|
|
3
|
-
* import {
|
|
3
|
+
* import { CONTROL_FIREWALL_RULES, OBSERVABILITY_PORTS } from '../../../../src/modules/infrastructure/firewalls/templates/firewall-rules.template';
|
|
4
4
|
*
|
|
5
5
|
* This file is kept for backward compatibility with existing CLI code.
|
|
6
6
|
* New code should use the shared template.
|
|
7
7
|
*/
|
|
8
|
-
export {
|
|
8
|
+
export { CONTROL_FIREWALL_RULES, OBSERVABILITY_PORTS, WORKLOAD_FIREWALL_RULES, WORKLOAD_PORTS, validateFirewallRules, getFirewallRulesForClusterType, } from '../../../../src/modules/infrastructure/firewalls/templates/firewall-rules.template';
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getFirewallRulesForClusterType = exports.validateFirewallRules = exports.WORKLOAD_PORTS = exports.WORKLOAD_FIREWALL_RULES = exports.OBSERVABILITY_PORTS = exports.
|
|
3
|
+
exports.getFirewallRulesForClusterType = exports.validateFirewallRules = exports.WORKLOAD_PORTS = exports.WORKLOAD_FIREWALL_RULES = exports.OBSERVABILITY_PORTS = exports.CONTROL_FIREWALL_RULES = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* @deprecated Import from shared template instead:
|
|
6
|
-
* import {
|
|
6
|
+
* import { CONTROL_FIREWALL_RULES, OBSERVABILITY_PORTS } from '../../../../src/modules/infrastructure/firewalls/templates/firewall-rules.template';
|
|
7
7
|
*
|
|
8
8
|
* This file is kept for backward compatibility with existing CLI code.
|
|
9
9
|
* New code should use the shared template.
|
|
10
10
|
*/
|
|
11
11
|
var firewall_rules_template_1 = require("../../../../src/modules/infrastructure/firewalls/templates/firewall-rules.template");
|
|
12
|
-
Object.defineProperty(exports, "
|
|
12
|
+
Object.defineProperty(exports, "CONTROL_FIREWALL_RULES", { enumerable: true, get: function () { return firewall_rules_template_1.CONTROL_FIREWALL_RULES; } });
|
|
13
13
|
Object.defineProperty(exports, "OBSERVABILITY_PORTS", { enumerable: true, get: function () { return firewall_rules_template_1.OBSERVABILITY_PORTS; } });
|
|
14
14
|
Object.defineProperty(exports, "WORKLOAD_FIREWALL_RULES", { enumerable: true, get: function () { return firewall_rules_template_1.WORKLOAD_FIREWALL_RULES; } });
|
|
15
15
|
Object.defineProperty(exports, "WORKLOAD_PORTS", { enumerable: true, get: function () { return firewall_rules_template_1.WORKLOAD_PORTS; } });
|
|
@@ -58,7 +58,7 @@ const label_service_1 = require("../../../src/modules/infrastructure/shared/serv
|
|
|
58
58
|
const cli_k3s_script_service_1 = require("../services/cli-k3s-script.service");
|
|
59
59
|
const cli_cluster_creator_service_1 = require("../services/cli-cluster-creator.service");
|
|
60
60
|
const cli_clusters_service_1 = require("../services/cli-clusters.service");
|
|
61
|
-
const
|
|
61
|
+
const cli_control_cluster_service_1 = require("../services/cli-control-cluster.service");
|
|
62
62
|
const cli_ssh_service_1 = require("../services/cli-ssh.service");
|
|
63
63
|
const cli_ca_service_1 = require("../services/cli-ca.service");
|
|
64
64
|
const cli_logger_service_1 = require("../services/cli-logger.service");
|
|
@@ -174,7 +174,7 @@ exports.CliInfrastructureModule = CliInfrastructureModule = __decorate([
|
|
|
174
174
|
label_service_1.LabelService,
|
|
175
175
|
kubernetes_service_1.KubernetesService,
|
|
176
176
|
cli_clusters_service_1.CliClustersService,
|
|
177
|
-
|
|
177
|
+
cli_control_cluster_service_1.CliControlClusterService,
|
|
178
178
|
cli_ssh_service_1.CliSshService,
|
|
179
179
|
cli_ca_service_1.CliCaService,
|
|
180
180
|
cli_logger_service_1.CliLoggerService,
|
|
@@ -233,7 +233,7 @@ exports.CliInfrastructureModule = CliInfrastructureModule = __decorate([
|
|
|
233
233
|
cluster_node_scaling_service_1.ClusterNodeScalingService,
|
|
234
234
|
],
|
|
235
235
|
exports: [
|
|
236
|
-
|
|
236
|
+
cli_control_cluster_service_1.CliControlClusterService,
|
|
237
237
|
cli_clusters_service_1.CliClustersService,
|
|
238
238
|
kubernetes_service_1.KubernetesService,
|
|
239
239
|
label_service_1.LabelService,
|
|
@@ -61,6 +61,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
61
61
|
const fs = __importStar(require("node:fs"));
|
|
62
62
|
const path = __importStar(require("node:path"));
|
|
63
63
|
const os = __importStar(require("node:os"));
|
|
64
|
+
const profile_manager_1 = require("../lib/profile-manager");
|
|
64
65
|
const https = __importStar(require("node:https"));
|
|
65
66
|
const hetzner_firewall_service_1 = require("../../../src/modules/providers/services/hetzner-firewall.service");
|
|
66
67
|
const cli_firewall_repository_1 = require("../lib/repositories/cli-firewall.repository");
|
|
@@ -167,7 +168,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
167
168
|
provider: cluster.provider,
|
|
168
169
|
caPublicKey,
|
|
169
170
|
operationId: operation.id,
|
|
170
|
-
deployObservabilityStack:
|
|
171
|
+
deployObservabilityStack: (0, cluster_entity_1.isControlClusterType)(cluster.clusterType),
|
|
171
172
|
postgresPassword,
|
|
172
173
|
redisPassword,
|
|
173
174
|
grafanaPassword,
|
|
@@ -190,6 +191,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
190
191
|
clusterFirewallId: firewallId || '',
|
|
191
192
|
nipIoCertEnabled: !clusterMeta?.zitadelDomain,
|
|
192
193
|
acmeStaging: !!clusterMeta?.acmeStaging,
|
|
194
|
+
useLatest: !!clusterMeta?.useLatest,
|
|
193
195
|
nipHostnameToken: cluster.nipHostnameToken || null,
|
|
194
196
|
envVnet: envVnet
|
|
195
197
|
? {
|
|
@@ -277,7 +279,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
277
279
|
sharedStorageVolumeSizeGb: cluster.sharedStorageVolumeSizeGb ?? undefined,
|
|
278
280
|
});
|
|
279
281
|
// Step 3c: Informational — Zitadel PAT injected on demand via sync-auth-domain
|
|
280
|
-
if (
|
|
282
|
+
if ((0, cluster_entity_1.isControlClusterType)(cluster.clusterType)) {
|
|
281
283
|
this.log(opId, 'ℹ️ Zitadel service account PAT will be injected when sync-auth-domain is called.');
|
|
282
284
|
this.log(opId, ' After DNS is configured, call: POST /api/v1/clusters/:id/dns-zone/sync-auth-domain');
|
|
283
285
|
}
|
|
@@ -332,6 +334,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
332
334
|
provider: cluster.provider,
|
|
333
335
|
caPublicKey,
|
|
334
336
|
operationId: operation.id,
|
|
337
|
+
useLatest: !!clusterMeta?.useLatest,
|
|
335
338
|
sharedStorage: workerSharedStorage,
|
|
336
339
|
});
|
|
337
340
|
const workerServer = await provider.createServer({
|
|
@@ -362,12 +365,15 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
362
365
|
try {
|
|
363
366
|
const firewallRecord = await this.firewallRepository.findById(firewallId);
|
|
364
367
|
if (firewallRecord) {
|
|
368
|
+
// Cluster-scoped name so destroy matches by exact name only.
|
|
369
|
+
const scopedFirewallName = `flui-control-firewall-${cluster.id}`;
|
|
365
370
|
firewallRecord.clusterId = cluster.id;
|
|
371
|
+
firewallRecord.name = scopedFirewallName;
|
|
366
372
|
// Get server IDs from cluster nodes
|
|
367
373
|
const serverIds = cluster.nodes.map((node) => node.providerResourceId);
|
|
368
374
|
firewallRecord.appliedToServerIds = serverIds;
|
|
369
375
|
await this.firewallRepository.save(firewallRecord);
|
|
370
|
-
// Update Hetzner firewall labels with cluster ID
|
|
376
|
+
// Update Hetzner firewall labels + name with cluster ID
|
|
371
377
|
try {
|
|
372
378
|
const existingLabels = Object.fromEntries(firewallRecord.labels.map((l) => [l.key, l.value]));
|
|
373
379
|
const updatedLabels = {
|
|
@@ -375,7 +381,7 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
375
381
|
'flui-cluster-id': cluster.id,
|
|
376
382
|
'flui-cluster-name': cluster.name,
|
|
377
383
|
};
|
|
378
|
-
await this.firewallService.updateFirewallLabels(firewallId, updatedLabels);
|
|
384
|
+
await this.firewallService.updateFirewallLabels(firewallId, updatedLabels, scopedFirewallName);
|
|
379
385
|
this.log(opId, `✅ Firewall ${firewallId} labels updated on Hetzner with cluster ID`);
|
|
380
386
|
}
|
|
381
387
|
catch (labelError) {
|
|
@@ -399,6 +405,23 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
399
405
|
}
|
|
400
406
|
catch (error) {
|
|
401
407
|
this.log(opId, `Failed to create cluster: ${error.message}`, 'ERROR');
|
|
408
|
+
// Roll back the pre-created firewall. If creation failed before the
|
|
409
|
+
// rename/label step it stays with a temporary name and no
|
|
410
|
+
// flui-cluster-id label, which makes it invisible to `env destroy`
|
|
411
|
+
// (it matches neither the label query nor the scoped-name fallback)
|
|
412
|
+
// and leaks as an orphan that blocks the next run's name.
|
|
413
|
+
const orphanFirewallId = operation.metadata?.firewallId;
|
|
414
|
+
if (orphanFirewallId) {
|
|
415
|
+
try {
|
|
416
|
+
await this.firewallService.deleteFirewall(orphanFirewallId);
|
|
417
|
+
await this.firewallRepository.delete(orphanFirewallId);
|
|
418
|
+
this.log(opId, `✅ Rolled back firewall ${orphanFirewallId} after failed cluster creation`);
|
|
419
|
+
}
|
|
420
|
+
catch (cleanupError) {
|
|
421
|
+
this.log(opId, `Failed to roll back firewall ${orphanFirewallId}: ${cleanupError.message}. ` +
|
|
422
|
+
`Delete it manually on the provider to avoid an orphan.`, 'WARN');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
402
425
|
// Mark cluster as FAILED
|
|
403
426
|
cluster.status = cluster_entity_1.ClusterStatus.ERROR;
|
|
404
427
|
await this.clusterRepository.save(cluster);
|
|
@@ -571,8 +594,10 @@ let CliClusterCreatorService = CliClusterCreatorService_1 = class CliClusterCrea
|
|
|
571
594
|
async createApiCredentialsSecret(operationId, masterIp, bootstrap) {
|
|
572
595
|
this.log(operationId, 'Patching Kubernetes secret with SSH CA keys + bootstrap vars...');
|
|
573
596
|
try {
|
|
574
|
-
// Load CLI CA keys to share with API for unified SSH access
|
|
575
|
-
|
|
597
|
+
// Load CLI CA keys to share with API for unified SSH access.
|
|
598
|
+
// CA keys are profile-scoped (~/.flui/profiles/<profile>/ca), not global —
|
|
599
|
+
// the old global path threw ENOENT and silently skipped the whole patch.
|
|
600
|
+
const caKeyDir = path.join(profile_manager_1.ProfileManager.getProfileDir(), 'ca');
|
|
576
601
|
const caPrivateKey = fs
|
|
577
602
|
.readFileSync(path.join(caKeyDir, 'ca_key'), 'utf8')
|
|
578
603
|
.trim();
|
|
@@ -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 {
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
448
|
-
//
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
fw.name
|
|
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
|
|
490
|
+
// STEP 2: Delete each firewall, retrying while the server still holds it
|
|
470
491
|
const deletePromises = allFirewalls.map(async (fw) => {
|
|
471
|
-
const maxRetries =
|
|
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
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
582
|
+
// Filter bootstrap keys for this control cluster
|
|
563
583
|
const bootstrapKeys = allKeys.filter((key) => {
|
|
564
584
|
const tags = key.tags || {};
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
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
|
+
}
|