@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
|
@@ -4,8 +4,11 @@ export default class AppList extends Command {
|
|
|
4
4
|
static readonly examples: string[];
|
|
5
5
|
static readonly flags: {
|
|
6
6
|
cluster: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
expanded: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
8
|
output: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
9
|
};
|
|
9
10
|
run(): Promise<void>;
|
|
11
|
+
private printGroup;
|
|
12
|
+
private printRow;
|
|
10
13
|
private colorStatus;
|
|
11
14
|
}
|
|
@@ -15,34 +15,30 @@ class AppList extends core_1.Command {
|
|
|
15
15
|
try {
|
|
16
16
|
const { id: clusterId } = await (0, resolve_cluster_1.resolveCluster)(flags.cluster);
|
|
17
17
|
const service = await cli_app_service_1.CliAppService.create(clusterId);
|
|
18
|
-
const
|
|
18
|
+
const groups = await service.listAppGroups();
|
|
19
19
|
spinner.stop();
|
|
20
20
|
if (flags.output === 'json') {
|
|
21
|
-
console.log(JSON.stringify(
|
|
21
|
+
console.log(JSON.stringify(groups, null, 2));
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
if (
|
|
24
|
+
if (groups.length === 0) {
|
|
25
25
|
console.log(chalk_1.default.yellow('\n No applications found in this cluster.\n'));
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
console.log(chalk_1.default.cyan('\n Applications\n'));
|
|
29
|
-
console.log(chalk_1.default.dim(` ${'NAME'.padEnd(
|
|
30
|
-
console.log(chalk_1.default.dim(' ' + '─'.repeat(
|
|
31
|
-
for (const
|
|
32
|
-
|
|
33
|
-
const kind = (app.kind || '').toLowerCase();
|
|
34
|
-
const exposure = (app.exposure || '').toLowerCase();
|
|
35
|
-
const replicas = String(app.replicas ?? '-');
|
|
36
|
-
const lastDeploy = app.lastDeployedAt
|
|
37
|
-
? new Date(app.lastDeployedAt).toLocaleString()
|
|
38
|
-
: chalk_1.default.dim('never');
|
|
39
|
-
// Pad before coloring so escape codes don't break column widths
|
|
40
|
-
const statusPadded = app.status.padEnd(14);
|
|
41
|
-
const coloredStatus = this.colorStatus(statusPadded);
|
|
42
|
-
console.log(` ${name.padEnd(28)} ${coloredStatus} ${replicas.padEnd(10)} ${kind.padEnd(14)} ${exposure.padEnd(10)} ${lastDeploy}`);
|
|
29
|
+
console.log(chalk_1.default.dim(` ${'NAME'.padEnd(30)} ${'STATUS'.padEnd(14)} ${'REPLICAS'.padEnd(10)} ${'KIND'.padEnd(12)} ${'EXPOSURE'.padEnd(10)} LAST DEPLOY`));
|
|
30
|
+
console.log(chalk_1.default.dim(' ' + '─'.repeat(92)));
|
|
31
|
+
for (const group of groups) {
|
|
32
|
+
this.printGroup(group, flags.expanded);
|
|
43
33
|
}
|
|
34
|
+
const appCount = groups.reduce((n, g) => n + g.componentCount, 0);
|
|
35
|
+
const composed = groups.filter((g) => g.type === 'composed').length;
|
|
44
36
|
console.log('');
|
|
45
|
-
console.log(chalk_1.default.dim(` ${
|
|
37
|
+
console.log(chalk_1.default.dim(` ${groups.length} app${groups.length === 1 ? '' : 's'}` +
|
|
38
|
+
(composed > 0 ? ` (${appCount} components)` : '') +
|
|
39
|
+
(composed > 0 && !flags.expanded
|
|
40
|
+
? chalk_1.default.dim(' · use --expanded to list components')
|
|
41
|
+
: '')));
|
|
46
42
|
console.log('');
|
|
47
43
|
}
|
|
48
44
|
catch (error) {
|
|
@@ -51,6 +47,58 @@ class AppList extends core_1.Command {
|
|
|
51
47
|
this.exit(1);
|
|
52
48
|
}
|
|
53
49
|
}
|
|
50
|
+
printGroup(group, expanded) {
|
|
51
|
+
if (group.type === 'standalone') {
|
|
52
|
+
const app = group.components[0];
|
|
53
|
+
this.printRow({
|
|
54
|
+
name: group.name,
|
|
55
|
+
status: group.status,
|
|
56
|
+
replicas: String(app?.replicas ?? '-'),
|
|
57
|
+
kind: (app?.kind || '').toLowerCase(),
|
|
58
|
+
exposure: (app?.exposure || '').toLowerCase(),
|
|
59
|
+
lastDeploy: app?.lastDeployedAt,
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const marker = expanded ? '▾' : '▸';
|
|
64
|
+
const primary = group.components.find((c) => c.isPrimary);
|
|
65
|
+
this.printRow({
|
|
66
|
+
name: `${marker} ${group.name} (${group.componentCount})`,
|
|
67
|
+
status: group.status,
|
|
68
|
+
replicas: '-',
|
|
69
|
+
kind: 'composed',
|
|
70
|
+
exposure: (primary?.exposure || '').toLowerCase(),
|
|
71
|
+
lastDeploy: undefined,
|
|
72
|
+
});
|
|
73
|
+
if (!expanded)
|
|
74
|
+
return;
|
|
75
|
+
const children = [...group.components].sort((a, b) => Number(b.isPrimary ?? false) - Number(a.isPrimary ?? false));
|
|
76
|
+
children.forEach((c, i) => {
|
|
77
|
+
const branch = i === children.length - 1 ? '└' : '├';
|
|
78
|
+
const short = c.slug.startsWith(`${group.slug}-`)
|
|
79
|
+
? c.slug.slice(group.slug.length + 1)
|
|
80
|
+
: c.slug;
|
|
81
|
+
this.printRow({
|
|
82
|
+
name: ` ${branch} ${short}${c.isPrimary ? chalk_1.default.dim(' (primary)') : ''}`,
|
|
83
|
+
status: c.status,
|
|
84
|
+
replicas: String(c.replicas ?? '-'),
|
|
85
|
+
kind: (c.kind || '').toLowerCase(),
|
|
86
|
+
exposure: (c.exposure || '').toLowerCase(),
|
|
87
|
+
lastDeploy: c.lastDeployedAt,
|
|
88
|
+
dim: true,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
printRow(r) {
|
|
93
|
+
const visibleLen = r.name.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
94
|
+
const namePad = r.name + ' '.repeat(Math.max(0, 30 - visibleLen));
|
|
95
|
+
const status = this.colorStatus(r.status.padEnd(14));
|
|
96
|
+
const last = r.lastDeploy
|
|
97
|
+
? new Date(r.lastDeploy).toLocaleString()
|
|
98
|
+
: chalk_1.default.dim('—');
|
|
99
|
+
const line = ` ${namePad} ${status} ${r.replicas.padEnd(10)} ${r.kind.padEnd(12)} ${r.exposure.padEnd(10)} ${last}`;
|
|
100
|
+
console.log(r.dim ? chalk_1.default.dim(line) : line);
|
|
101
|
+
}
|
|
54
102
|
colorStatus(status) {
|
|
55
103
|
const s = status.trim().toLowerCase();
|
|
56
104
|
if (s === 'running')
|
|
@@ -67,6 +115,7 @@ class AppList extends core_1.Command {
|
|
|
67
115
|
AppList.description = 'List all applications in the cluster';
|
|
68
116
|
AppList.examples = [
|
|
69
117
|
'<%= config.bin %> <%= command.id %>',
|
|
118
|
+
'<%= config.bin %> <%= command.id %> --expanded',
|
|
70
119
|
'<%= config.bin %> <%= command.id %> --output json',
|
|
71
120
|
];
|
|
72
121
|
AppList.flags = {
|
|
@@ -74,6 +123,11 @@ AppList.flags = {
|
|
|
74
123
|
char: 'c',
|
|
75
124
|
description: 'Cluster name or ID (default: auto-detect when only one cluster exists)',
|
|
76
125
|
}),
|
|
126
|
+
expanded: core_1.Flags.boolean({
|
|
127
|
+
char: 'x',
|
|
128
|
+
description: 'Show the individual components of composed apps',
|
|
129
|
+
default: false,
|
|
130
|
+
}),
|
|
77
131
|
output: core_1.Flags.string({
|
|
78
132
|
char: 'o',
|
|
79
133
|
description: 'Output format',
|
|
@@ -10,6 +10,7 @@ export default class AppStatus extends Command {
|
|
|
10
10
|
output: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
11
|
};
|
|
12
12
|
run(): Promise<void>;
|
|
13
|
+
private printEndpoint;
|
|
13
14
|
private printContainer;
|
|
14
15
|
private colorStatus;
|
|
15
16
|
}
|
|
@@ -16,10 +16,13 @@ class AppStatus extends core_1.Command {
|
|
|
16
16
|
const { id: clusterId } = await (0, resolve_cluster_1.resolveCluster)(flags.cluster);
|
|
17
17
|
const service = await cli_app_service_1.CliAppService.create(clusterId);
|
|
18
18
|
const app = await service.getAppByName(args.name);
|
|
19
|
-
const runtime = await
|
|
19
|
+
const [runtime, endpoints] = await Promise.all([
|
|
20
|
+
service.getRuntime(app.id),
|
|
21
|
+
service.listEndpoints(app.id).catch(() => []),
|
|
22
|
+
]);
|
|
20
23
|
spinner.stop();
|
|
21
24
|
if (flags.output === 'json') {
|
|
22
|
-
console.log(JSON.stringify({ app, runtime }, null, 2));
|
|
25
|
+
console.log(JSON.stringify({ app, runtime, endpoints }, null, 2));
|
|
23
26
|
return;
|
|
24
27
|
}
|
|
25
28
|
console.log(chalk_1.default.cyan(`\n ${app.name}\n`));
|
|
@@ -45,6 +48,11 @@ class AppStatus extends core_1.Command {
|
|
|
45
48
|
for (const c of runtime.containers)
|
|
46
49
|
this.printContainer(c);
|
|
47
50
|
}
|
|
51
|
+
if (endpoints.length > 0) {
|
|
52
|
+
console.log(chalk_1.default.cyan('\n Endpoints\n'));
|
|
53
|
+
for (const e of endpoints)
|
|
54
|
+
this.printEndpoint(e);
|
|
55
|
+
}
|
|
48
56
|
console.log('');
|
|
49
57
|
}
|
|
50
58
|
catch (error) {
|
|
@@ -53,6 +61,23 @@ class AppStatus extends core_1.Command {
|
|
|
53
61
|
this.exit(1);
|
|
54
62
|
}
|
|
55
63
|
}
|
|
64
|
+
printEndpoint(e) {
|
|
65
|
+
const scheme = e.tlsEnabled ? 'https' : 'http';
|
|
66
|
+
const url = `${scheme}://${e.fqdn}`;
|
|
67
|
+
console.log(` ${chalk_1.default.bold('URL:')} ${chalk_1.default.cyan(url)}`);
|
|
68
|
+
const meta = [
|
|
69
|
+
e.endpointType,
|
|
70
|
+
e.hostnameMode,
|
|
71
|
+
e.tlsEnabled ? 'tls' : 'no-tls',
|
|
72
|
+
]
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.join(' · ');
|
|
75
|
+
console.log(` ${chalk_1.default.dim(meta)}`);
|
|
76
|
+
if (e.certificateStatus && e.certificateStatus !== 'ISSUED') {
|
|
77
|
+
const color = e.certificateStatus === 'FAILED' ? chalk_1.default.red : chalk_1.default.yellow;
|
|
78
|
+
console.log(` ${chalk_1.default.bold('Cert:')} ${color(e.certificateStatus)}${e.certificateMessage ? ` — ${e.certificateMessage}` : ''}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
56
81
|
printContainer(c) {
|
|
57
82
|
console.log(` ${chalk_1.default.bold(c.name)}`);
|
|
58
83
|
console.log(` ${chalk_1.default.dim('image:')} ${c.image}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class ClusterDestroy extends Command {
|
|
3
|
-
static readonly description = "Permanently destroy a workload cluster and all its nodes. For the
|
|
3
|
+
static readonly description = "Permanently destroy a workload cluster and all its nodes. For the control cluster use `flui env destroy`.";
|
|
4
4
|
static readonly examples: string[];
|
|
5
5
|
static readonly args: {
|
|
6
6
|
cluster: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
@@ -30,7 +30,7 @@ class ClusterDestroy extends core_1.Command {
|
|
|
30
30
|
}
|
|
31
31
|
const { id: clusterId, name: clusterName, entity } = resolved;
|
|
32
32
|
if (entity.metadata?.isObservabilityCluster) {
|
|
33
|
-
this.error(`"${clusterName}" is the
|
|
33
|
+
this.error(`"${clusterName}" is the control cluster. Use \`flui env destroy\` instead.`, { exit: 1 });
|
|
34
34
|
}
|
|
35
35
|
console.log(chalk_1.default.red('\n⚠️ DESTROY Workload Cluster\n'));
|
|
36
36
|
console.log(` ${chalk_1.default.bold('Name:')} ${clusterName}`);
|
|
@@ -111,7 +111,7 @@ class ClusterDestroy extends core_1.Command {
|
|
|
111
111
|
return false;
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
ClusterDestroy.description = 'Permanently destroy a workload cluster and all its nodes. For the
|
|
114
|
+
ClusterDestroy.description = 'Permanently destroy a workload cluster and all its nodes. For the control cluster use `flui env destroy`.';
|
|
115
115
|
ClusterDestroy.examples = [
|
|
116
116
|
'<%= config.bin %> <%= command.id %> my-workload-cluster',
|
|
117
117
|
'<%= config.bin %> <%= command.id %> my-workload-cluster --force',
|
|
@@ -16,6 +16,9 @@ export default class Deploy extends Command {
|
|
|
16
16
|
'no-build': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
17
17
|
image: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
18
|
'skip-endpoint': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
'cert-challenge': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
+
'cert-provider': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
hostname: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
22
|
'no-wait': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
20
23
|
'validate-only': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
21
24
|
'skip-checks': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
@@ -258,6 +258,13 @@ class Deploy extends core_1.Command {
|
|
|
258
258
|
clusterId,
|
|
259
259
|
...(flags.name ? { displayName: flags.name } : {}),
|
|
260
260
|
...(flags.domain ? { domain: flags.domain } : {}),
|
|
261
|
+
...(flags['cert-challenge']
|
|
262
|
+
? { certChallenge: flags['cert-challenge'] }
|
|
263
|
+
: {}),
|
|
264
|
+
...(flags['cert-provider']
|
|
265
|
+
? { certificateProvider: flags['cert-provider'] }
|
|
266
|
+
: {}),
|
|
267
|
+
...(flags.hostname ? { hostnameMode: flags.hostname } : {}),
|
|
261
268
|
...(flags['skip-endpoint'] ? { skipEndpoint: true } : {}),
|
|
262
269
|
...(Object.keys(envOverrides).length > 0
|
|
263
270
|
? { envOverrides, userInputs: envOverrides }
|
|
@@ -525,6 +532,18 @@ Deploy.flags = {
|
|
|
525
532
|
description: 'Skip DNS and TLS provisioning (kind:CatalogApp only)',
|
|
526
533
|
default: false,
|
|
527
534
|
}),
|
|
535
|
+
'cert-challenge': core_1.Flags.string({
|
|
536
|
+
description: 'ACME challenge for the app endpoint (kind:CatalogApp). http-01 works without a DNS zone and forces a per-host cert; dns-01 needs a cluster DNS zone with a wildcard issuer. Default: derived from cluster config.',
|
|
537
|
+
options: ['http-01', 'dns-01'],
|
|
538
|
+
}),
|
|
539
|
+
'cert-provider': core_1.Flags.string({
|
|
540
|
+
description: 'Certificate issuer for the app endpoint (kind:CatalogApp). Default: cluster default.',
|
|
541
|
+
options: ['lets-encrypt', 'lets-encrypt-staging'],
|
|
542
|
+
}),
|
|
543
|
+
hostname: core_1.Flags.string({
|
|
544
|
+
description: 'How the app is exposed (kind:CatalogApp): ip (nip.io) or domain (cluster DNS zone). Default: derived from manifest/cluster.',
|
|
545
|
+
options: ['ip', 'domain'],
|
|
546
|
+
}),
|
|
528
547
|
'no-wait': core_1.Flags.boolean({
|
|
529
548
|
description: 'Alias for --detach (kind:CatalogApp compat)',
|
|
530
549
|
default: false,
|
|
@@ -43,7 +43,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
43
43
|
const path = __importStar(require("node:path"));
|
|
44
44
|
const os = __importStar(require("node:os"));
|
|
45
45
|
const nest_app_1 = require("../../lib/nest-app");
|
|
46
|
-
const
|
|
46
|
+
const cli_control_cluster_service_1 = require("../../services/cli-control-cluster.service");
|
|
47
47
|
const cli_ssh_service_1 = require("../../services/cli-ssh.service");
|
|
48
48
|
const config_storage_1 = require("../../lib/config-storage");
|
|
49
49
|
const encryption_service_1 = require("../../../../src/modules/shared/encryption/services/encryption.service");
|
|
@@ -58,12 +58,12 @@ class DevCreds extends core_1.Command {
|
|
|
58
58
|
let spinner = (0, ora_1.default)('Reading cluster configuration...').start();
|
|
59
59
|
try {
|
|
60
60
|
const app = await (0, nest_app_1.getNestApp)();
|
|
61
|
-
const
|
|
61
|
+
const controlService = app.get(cli_control_cluster_service_1.CliControlClusterService);
|
|
62
62
|
const encryptionService = app.get(encryption_service_1.EncryptionService);
|
|
63
63
|
const sshService = app.get(cli_ssh_service_1.CliSshService);
|
|
64
|
-
const cluster = await
|
|
64
|
+
const cluster = await controlService.getControlCluster();
|
|
65
65
|
if (!cluster) {
|
|
66
|
-
spinner.fail('No
|
|
66
|
+
spinner.fail('No control cluster found');
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
if (cluster.status !== cluster_entity_1.ClusterStatus.READY) {
|
|
@@ -89,14 +89,6 @@ class DevCreds extends core_1.Command {
|
|
|
89
89
|
spinner = (0, ora_1.default)('Reading flui-secrets via SSH...').start();
|
|
90
90
|
const fluiSecrets = await this.readFluiSecrets(sshService, masterIp);
|
|
91
91
|
spinner.succeed('flui-secrets read');
|
|
92
|
-
// OIDC issuer from flui-api-config — used to point local API at the
|
|
93
|
-
// public Zitadel admin URL instead of the in-cluster service DNS.
|
|
94
|
-
spinner = (0, ora_1.default)('Reading OIDC issuer from flui-api-config...').start();
|
|
95
|
-
const oidcIssuer = await this.readOidcIssuer(sshService, masterIp);
|
|
96
|
-
if (oidcIssuer)
|
|
97
|
-
spinner.succeed(`OIDC issuer: ${oidcIssuer}`);
|
|
98
|
-
else
|
|
99
|
-
spinner.warn('OIDC issuer not found in flui-api-config');
|
|
100
92
|
// Local encryption key — shared with API via ~/.flui/encryption.key.
|
|
101
93
|
const encryptionKeyPath = path.join(os.homedir(), '.flui', 'encryption.key');
|
|
102
94
|
const encryptionKey = fs.existsSync(encryptionKeyPath)
|
|
@@ -111,7 +103,6 @@ class DevCreds extends core_1.Command {
|
|
|
111
103
|
passwords,
|
|
112
104
|
encryptionKey,
|
|
113
105
|
fluiSecrets,
|
|
114
|
-
oidcIssuer,
|
|
115
106
|
});
|
|
116
107
|
this.printSummary(envLocalPath, envVars);
|
|
117
108
|
if (fluiSecrets.fluiApiKey) {
|
|
@@ -160,7 +151,7 @@ class DevCreds extends core_1.Command {
|
|
|
160
151
|
return null;
|
|
161
152
|
}
|
|
162
153
|
buildEnvVars(opts) {
|
|
163
|
-
const { passwords, encryptionKey, fluiSecrets
|
|
154
|
+
const { passwords, encryptionKey, fluiSecrets } = opts;
|
|
164
155
|
const envVars = {
|
|
165
156
|
DB_PASSWORD: passwords.postgres,
|
|
166
157
|
REDIS_PASSWORD: passwords.redis,
|
|
@@ -180,8 +171,6 @@ class DevCreds extends core_1.Command {
|
|
|
180
171
|
envVars.SSH_CA_PUBLIC_KEY = fluiSecrets.sshCaPublicKey;
|
|
181
172
|
if (fluiSecrets.zitadelPat)
|
|
182
173
|
envVars.ZITADEL_SERVICE_ACCOUNT_PAT = fluiSecrets.zitadelPat;
|
|
183
|
-
if (oidcIssuer)
|
|
184
|
-
envVars.OIDC_PROVIDER_ADMIN_URL = oidcIssuer;
|
|
185
174
|
return envVars;
|
|
186
175
|
}
|
|
187
176
|
writeEnvFile(envLocalPath, apiDir, envVars, backup) {
|
|
@@ -228,16 +217,6 @@ class DevCreds extends core_1.Command {
|
|
|
228
217
|
return {};
|
|
229
218
|
}
|
|
230
219
|
}
|
|
231
|
-
async readOidcIssuer(sshService, masterIp) {
|
|
232
|
-
try {
|
|
233
|
-
const raw = await sshService.sshExec(masterIp, "kubectl -n flui-system get configmap flui-api-config -o jsonpath='{.data.OIDC_ISSUER}'");
|
|
234
|
-
const trimmed = raw.trim();
|
|
235
|
-
return trimmed || undefined;
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
return undefined;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
220
|
async resolveApiPath(explicit, save) {
|
|
242
221
|
const storage = new config_storage_1.ConfigStorage();
|
|
243
222
|
const resolver = new preferences_resolver_1.PreferencesResolver(storage);
|
|
@@ -267,7 +246,7 @@ DevCreds.description = 'Developer-only: write cluster secrets (DB/Redis password
|
|
|
267
246
|
DevCreds.examples = [
|
|
268
247
|
'<%= config.bin %> <%= command.id %>',
|
|
269
248
|
'<%= config.bin %> <%= command.id %> --dry-run',
|
|
270
|
-
'<%= config.bin %> <%= command.id %> --api-path ../flui
|
|
249
|
+
'<%= config.bin %> <%= command.id %> --api-path ../flui-core',
|
|
271
250
|
];
|
|
272
251
|
DevCreds.flags = {
|
|
273
252
|
'dry-run': core_1.Flags.boolean({
|
|
@@ -10,7 +10,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
10
10
|
const node_util_1 = require("node:util");
|
|
11
11
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
12
12
|
const nest_app_1 = require("../../lib/nest-app");
|
|
13
|
-
const
|
|
13
|
+
const cli_control_cluster_service_1 = require("../../services/cli-control-cluster.service");
|
|
14
14
|
const cli_ssh_service_1 = require("../../services/cli-ssh.service");
|
|
15
15
|
const cluster_entity_1 = require("../../../../src/modules/infrastructure/clusters/entities/cluster.entity");
|
|
16
16
|
const FORWARDS = {
|
|
@@ -41,7 +41,7 @@ const FORWARDS = {
|
|
|
41
41
|
localPort: 3001,
|
|
42
42
|
remotePort: 3000,
|
|
43
43
|
service: 'svc/grafana',
|
|
44
|
-
namespace: 'flui-
|
|
44
|
+
namespace: 'flui-control',
|
|
45
45
|
needsKubectl: true,
|
|
46
46
|
},
|
|
47
47
|
vmsingle: {
|
|
@@ -49,7 +49,7 @@ const FORWARDS = {
|
|
|
49
49
|
localPort: 9090,
|
|
50
50
|
remotePort: 8428,
|
|
51
51
|
service: 'svc/vmsingle',
|
|
52
|
-
namespace: 'flui-
|
|
52
|
+
namespace: 'flui-control',
|
|
53
53
|
needsKubectl: true,
|
|
54
54
|
},
|
|
55
55
|
loki: {
|
|
@@ -57,7 +57,7 @@ const FORWARDS = {
|
|
|
57
57
|
localPort: 3100,
|
|
58
58
|
remotePort: 3100,
|
|
59
59
|
service: 'svc/loki',
|
|
60
|
-
namespace: 'flui-
|
|
60
|
+
namespace: 'flui-control',
|
|
61
61
|
needsKubectl: true,
|
|
62
62
|
},
|
|
63
63
|
};
|
|
@@ -68,10 +68,10 @@ class DevTunnel extends core_1.Command {
|
|
|
68
68
|
let masterIp;
|
|
69
69
|
try {
|
|
70
70
|
const app = await (0, nest_app_1.getNestApp)();
|
|
71
|
-
const
|
|
72
|
-
const cluster = await
|
|
71
|
+
const controlService = app.get(cli_control_cluster_service_1.CliControlClusterService);
|
|
72
|
+
const cluster = await controlService.getControlCluster();
|
|
73
73
|
if (!cluster) {
|
|
74
|
-
spinner.fail('No
|
|
74
|
+
spinner.fail('No control cluster found');
|
|
75
75
|
console.log(chalk_1.default.yellow('\n⚠️ Run `flui env create` first.\n'));
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
@@ -239,7 +239,7 @@ class DevTunnel extends core_1.Command {
|
|
|
239
239
|
return `sh -c '${lines.join('\n')}'`;
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
DevTunnel.description = 'Open SSH tunnels from localhost to the
|
|
242
|
+
DevTunnel.description = 'Open SSH tunnels from localhost to the control cluster services.\n' +
|
|
243
243
|
'On the remote side runs kubectl port-forward against the in-cluster Services,\n' +
|
|
244
244
|
'so no NodePort or kube-API exposure is required. Stay in foreground; CTRL-C to close.';
|
|
245
245
|
DevTunnel.examples = [
|
|
@@ -7,7 +7,7 @@ const core_1 = require("@oclif/core");
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const ora_1 = __importDefault(require("ora"));
|
|
9
9
|
const nest_app_1 = require("../../lib/nest-app");
|
|
10
|
-
const
|
|
10
|
+
const cli_control_cluster_service_1 = require("../../services/cli-control-cluster.service");
|
|
11
11
|
const cluster_capacity_service_1 = require("../../../../src/modules/infrastructure/clusters/services/cluster-capacity.service");
|
|
12
12
|
const context_banner_1 = require("../../lib/context-banner");
|
|
13
13
|
class EnvCapacity extends core_1.Command {
|
|
@@ -17,11 +17,11 @@ class EnvCapacity extends core_1.Command {
|
|
|
17
17
|
const spinner = (0, ora_1.default)('Computing capacity plan...').start();
|
|
18
18
|
try {
|
|
19
19
|
const app = await (0, nest_app_1.getNestApp)();
|
|
20
|
-
const
|
|
20
|
+
const controlService = app.get(cli_control_cluster_service_1.CliControlClusterService);
|
|
21
21
|
const capacityService = app.get(cluster_capacity_service_1.ClusterCapacityService);
|
|
22
|
-
const cluster = await
|
|
22
|
+
const cluster = await controlService.getControlCluster();
|
|
23
23
|
if (!cluster) {
|
|
24
|
-
spinner.fail('No
|
|
24
|
+
spinner.fail('No control cluster found');
|
|
25
25
|
console.log(chalk_1.default.yellow('\n⚠️ Create a cluster first: ' + chalk_1.default.cyan('flui env create\n')));
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class EnvCreate extends Command {
|
|
3
|
-
static readonly description = "Create
|
|
3
|
+
static readonly description = "Create control cluster infrastructure on K3s";
|
|
4
4
|
static readonly examples: string[];
|
|
5
5
|
static readonly flags: {
|
|
6
6
|
provider: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -14,8 +14,11 @@ export default class EnvCreate extends Command {
|
|
|
14
14
|
'firewall-ip': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
15
|
'auth-mode': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
16
|
'acme-staging': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
latest: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
17
18
|
'no-shared-storage': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
18
19
|
'shared-storage-size': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
20
|
};
|
|
21
|
+
/** The sole provider with configured credentials, or undefined if none/both. */
|
|
22
|
+
private detectConfiguredProvider;
|
|
20
23
|
run(): Promise<void>;
|
|
21
24
|
}
|