@rulebricks/cli 2.1.7 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -16
- package/cluster-setup/aws/README.md +96 -47
- package/cluster-setup/aws/check-aws-access.sh +216 -52
- package/cluster-setup/aws/parameters.json +13 -0
- package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
- package/cluster-setup/azure/README.md +103 -55
- package/cluster-setup/azure/check-aks-prereqs.sh +236 -56
- package/cluster-setup/azure/parameters.json +30 -0
- package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
- package/cluster-setup/gcp/README.md +51 -34
- package/cluster-setup/gcp/check-gke-prereqs.sh +222 -60
- package/dist/commands/backup.d.ts +5 -0
- package/dist/commands/backup.js +104 -0
- package/dist/commands/deploy.d.ts +3 -1
- package/dist/commands/deploy.js +226 -326
- package/dist/commands/destroy.d.ts +1 -1
- package/dist/commands/destroy.js +73 -123
- package/dist/commands/init.d.ts +5 -1
- package/dist/commands/init.js +78 -54
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +74 -0
- package/dist/commands/open.d.ts +1 -1
- package/dist/commands/open.js +4 -12
- package/dist/commands/redeploy.d.ts +6 -0
- package/dist/commands/redeploy.js +310 -0
- package/dist/commands/restore.d.ts +5 -0
- package/dist/commands/restore.js +338 -0
- package/dist/commands/status.js +62 -49
- package/dist/commands/upgrade.js +74 -51
- package/dist/components/DNSWaitScreen.d.ts +5 -1
- package/dist/components/DNSWaitScreen.js +47 -41
- package/dist/components/Wizard/WizardContext.d.ts +157 -36
- package/dist/components/Wizard/WizardContext.js +872 -160
- package/dist/components/Wizard/steps/CloudProviderStep.js +192 -107
- package/dist/components/Wizard/steps/DomainStep.js +5 -24
- package/dist/components/Wizard/steps/ExternalServicesStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ExternalServicesStep.js +645 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +2 -1
- package/dist/components/Wizard/steps/FeatureConfigStep.js +739 -425
- package/dist/components/Wizard/steps/FeaturesStep.js +31 -35
- package/dist/components/Wizard/steps/ObservabilityStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ObservabilityStep.js +137 -0
- package/dist/components/Wizard/steps/ReviewStep.d.ts +2 -1
- package/dist/components/Wizard/steps/ReviewStep.js +56 -12
- package/dist/components/Wizard/steps/StorageStep.d.ts +9 -0
- package/dist/components/Wizard/steps/StorageStep.js +592 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +20 -21
- package/dist/components/Wizard/steps/VersionStep.js +45 -23
- package/dist/components/Wizard/steps/index.d.ts +3 -3
- package/dist/components/Wizard/steps/index.js +3 -3
- package/dist/components/common/CommandApproval.d.ts +12 -0
- package/dist/components/common/CommandApproval.js +91 -0
- package/dist/components/common/DeploymentPicker.d.ts +14 -0
- package/dist/components/common/DeploymentPicker.js +16 -0
- package/dist/components/common/index.d.ts +2 -0
- package/dist/components/common/index.js +2 -0
- package/dist/index.js +94 -62
- package/dist/lib/cloudCli.d.ts +134 -63
- package/dist/lib/cloudCli.js +512 -220
- package/dist/lib/clusterSetupDefaults.d.ts +30 -0
- package/dist/lib/clusterSetupDefaults.js +64 -0
- package/dist/lib/commandApproval.d.ts +26 -0
- package/dist/lib/commandApproval.js +114 -0
- package/dist/lib/config.d.ts +12 -10
- package/dist/lib/config.js +91 -33
- package/dist/lib/configFixtures.d.ts +5 -0
- package/dist/lib/configFixtures.js +513 -0
- package/dist/lib/deploymentHealth.d.ts +32 -0
- package/dist/lib/deploymentHealth.js +157 -0
- package/dist/lib/dns.d.ts +1 -1
- package/dist/lib/dns.js +19 -1
- package/dist/lib/dns.test.d.ts +1 -0
- package/dist/lib/dns.test.js +27 -0
- package/dist/lib/dockerHub.d.ts +12 -1
- package/dist/lib/dockerHub.js +18 -8
- package/dist/lib/helm.d.ts +4 -0
- package/dist/lib/helm.js +16 -0
- package/dist/lib/helmValues.d.ts +25 -0
- package/dist/lib/helmValues.js +1762 -289
- package/dist/lib/helmValues.test.d.ts +1 -0
- package/dist/lib/helmValues.test.js +966 -0
- package/dist/lib/htpasswd.d.ts +1 -0
- package/dist/lib/htpasswd.js +15 -0
- package/dist/lib/kubernetes.d.ts +124 -17
- package/dist/lib/kubernetes.js +576 -145
- package/dist/lib/secrets.d.ts +23 -0
- package/dist/lib/secrets.js +158 -0
- package/dist/lib/validateValues.d.ts +31 -0
- package/dist/lib/validateValues.js +253 -0
- package/dist/lib/versions.d.ts +82 -11
- package/dist/lib/versions.js +131 -31
- package/dist/lib/versions.test.d.ts +1 -0
- package/dist/lib/versions.test.js +81 -0
- package/dist/lib/wizardSteps.d.ts +14 -0
- package/dist/lib/wizardSteps.js +23 -0
- package/dist/lib/workloadIdentity.d.ts +26 -0
- package/dist/lib/workloadIdentity.js +323 -0
- package/dist/lib/workloadIdentity.test.d.ts +1 -0
- package/dist/lib/workloadIdentity.test.js +57 -0
- package/dist/types/index.d.ts +1860 -164
- package/dist/types/index.js +518 -295
- package/package.json +9 -4
- package/schema/values.schema.json +1934 -0
- package/cluster-setup/aws/cluster.yaml +0 -33
- package/cluster-setup/azure/main.bicep +0 -282
- package/cluster-setup/azure/main.parameters.json +0 -21
- package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
- package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
- package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
- package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
- package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
- package/dist/components/Wizard/steps/TierStep.js +0 -29
- package/dist/lib/terraform.d.ts +0 -66
- package/dist/lib/terraform.js +0 -754
- package/terraform/aws/main.tf +0 -355
- package/terraform/azure/main.tf +0 -371
- package/terraform/gcp/main.tf +0 -407
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CloudProvider } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Detects the IAM roles / managed identities / buckets that the bundled
|
|
4
|
+
* cluster-setup templates (cluster-setup/aws|azure|gcp) provision, so the wizard
|
|
5
|
+
* can preselect them as sensible defaults. The user can always arrow to a
|
|
6
|
+
* different option, and when nothing matches the behavior is unchanged.
|
|
7
|
+
*
|
|
8
|
+
* Naming conventions (parameterized by cluster name, default rulebricks-cluster):
|
|
9
|
+
* AWS (CloudFormation): roles `${cluster}-metrics`, `${cluster}-decision-logs`,
|
|
10
|
+
* `${cluster}-backups`; buckets `${cluster}-decision-logs-*`,
|
|
11
|
+
* `${cluster}-backups-*`.
|
|
12
|
+
* Azure (Bicep): UAMIs `${cluster}-metrics`, `${cluster}-decision-logs`,
|
|
13
|
+
* `${cluster}-backups`; blob containers `decision-logs`, `backups`.
|
|
14
|
+
* GCP (docs): service account `rulebricks-vector` (decision logs only).
|
|
15
|
+
*/
|
|
16
|
+
export type ClusterSetupCategory = "metrics-identity" | "decision-logs-identity" | "backups-identity" | "decision-logs-bucket" | "backups-bucket" | "decision-logs-container" | "backups-container";
|
|
17
|
+
export interface ClusterSetupDetectOptions {
|
|
18
|
+
provider?: CloudProvider | null;
|
|
19
|
+
/** Cluster name used to build the most specific match. */
|
|
20
|
+
clusterName?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns the index of the best cluster-setup match in `candidates`, or -1 when
|
|
24
|
+
* none match. Matching is case-insensitive and prefers more specific patterns.
|
|
25
|
+
*/
|
|
26
|
+
export declare function findClusterSetupDefaultIndex(candidates: string[], category: ClusterSetupCategory, options?: ClusterSetupDetectOptions): number;
|
|
27
|
+
/**
|
|
28
|
+
* Convenience wrapper returning the matched candidate string (or undefined).
|
|
29
|
+
*/
|
|
30
|
+
export declare function findClusterSetupDefault(candidates: string[], category: ClusterSetupCategory, options?: ClusterSetupDetectOptions): string | undefined;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const DEFAULT_CLUSTER = "rulebricks-cluster";
|
|
2
|
+
/**
|
|
3
|
+
* Substrings to look for, ordered most-specific to least-specific. The first
|
|
4
|
+
* candidate matching the highest-priority pattern wins.
|
|
5
|
+
*/
|
|
6
|
+
function patternsFor(category, cluster) {
|
|
7
|
+
// The consolidated cluster-setup provisions a single `${cluster}-rulebricks`
|
|
8
|
+
// identity and a single `${cluster}-data` bucket/container. Older split
|
|
9
|
+
// resources (`-decision-logs`, `-backups`, `-metrics`) are kept as lower-
|
|
10
|
+
// priority fallbacks so the wizard still preselects sensibly on legacy infra.
|
|
11
|
+
switch (category) {
|
|
12
|
+
case "metrics-identity":
|
|
13
|
+
return [`${cluster}-rulebricks`, `${cluster}-metrics`, "-rulebricks", "-metrics"];
|
|
14
|
+
case "decision-logs-identity":
|
|
15
|
+
case "backups-identity":
|
|
16
|
+
return [
|
|
17
|
+
`${cluster}-rulebricks`,
|
|
18
|
+
"-rulebricks",
|
|
19
|
+
`${cluster}-decision-logs`,
|
|
20
|
+
"-decision-logs",
|
|
21
|
+
`${cluster}-backups`,
|
|
22
|
+
"-backups",
|
|
23
|
+
];
|
|
24
|
+
case "decision-logs-bucket":
|
|
25
|
+
case "backups-bucket":
|
|
26
|
+
return [
|
|
27
|
+
`${cluster}-data`,
|
|
28
|
+
"-data",
|
|
29
|
+
`${cluster}-decision-logs`,
|
|
30
|
+
"-decision-logs",
|
|
31
|
+
`${cluster}-backups`,
|
|
32
|
+
"-backups",
|
|
33
|
+
];
|
|
34
|
+
case "decision-logs-container":
|
|
35
|
+
case "backups-container":
|
|
36
|
+
return [`${cluster}-data`, "-data", "rulebricks", "decision-logs", "backups"];
|
|
37
|
+
default:
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns the index of the best cluster-setup match in `candidates`, or -1 when
|
|
43
|
+
* none match. Matching is case-insensitive and prefers more specific patterns.
|
|
44
|
+
*/
|
|
45
|
+
export function findClusterSetupDefaultIndex(candidates, category, options = {}) {
|
|
46
|
+
if (candidates.length === 0)
|
|
47
|
+
return -1;
|
|
48
|
+
const cluster = (options.clusterName || DEFAULT_CLUSTER).trim() || DEFAULT_CLUSTER;
|
|
49
|
+
const lowered = candidates.map((c) => c.toLowerCase());
|
|
50
|
+
for (const pattern of patternsFor(category, cluster)) {
|
|
51
|
+
const needle = pattern.toLowerCase();
|
|
52
|
+
const index = lowered.findIndex((c) => c.includes(needle));
|
|
53
|
+
if (index >= 0)
|
|
54
|
+
return index;
|
|
55
|
+
}
|
|
56
|
+
return -1;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Convenience wrapper returning the matched candidate string (or undefined).
|
|
60
|
+
*/
|
|
61
|
+
export function findClusterSetupDefault(candidates, category, options = {}) {
|
|
62
|
+
const index = findClusterSetupDefaultIndex(candidates, category, options);
|
|
63
|
+
return index >= 0 ? candidates[index] : undefined;
|
|
64
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CloudProvider } from "../types/index.js";
|
|
2
|
+
export type CommandApprovalDecision = "approve" | "deny";
|
|
3
|
+
export type CommandApprovalScope = "once" | "all-like" | "all";
|
|
4
|
+
export interface CommandApprovalRequest {
|
|
5
|
+
intent: string;
|
|
6
|
+
command: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
provider?: CloudProvider;
|
|
9
|
+
mutating?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface PendingCommandApproval extends CommandApprovalRequest {
|
|
12
|
+
id: number;
|
|
13
|
+
}
|
|
14
|
+
type Listener = () => void;
|
|
15
|
+
export declare class CommandDeniedError extends Error {
|
|
16
|
+
readonly command: string;
|
|
17
|
+
readonly intent: string;
|
|
18
|
+
constructor(command: string, intent: string);
|
|
19
|
+
}
|
|
20
|
+
export declare function setCommandApprovalInteractive(value: boolean): void;
|
|
21
|
+
export declare function subscribeCommandApprovals(listener: Listener): () => void;
|
|
22
|
+
export declare function getCurrentCommandApproval(): PendingCommandApproval | null;
|
|
23
|
+
export declare function respondToCommandApproval(id: number, decision: CommandApprovalDecision, scope?: CommandApprovalScope): void;
|
|
24
|
+
export declare function requestCommandApproval(req: CommandApprovalRequest): Promise<CommandApprovalDecision>;
|
|
25
|
+
export declare function approveCloudCommandOrThrow(req: CommandApprovalRequest): Promise<void>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export class CommandDeniedError extends Error {
|
|
2
|
+
command;
|
|
3
|
+
intent;
|
|
4
|
+
constructor(command, intent) {
|
|
5
|
+
super(`User denied cloud CLI command: ${command}`);
|
|
6
|
+
this.name = "CommandDeniedError";
|
|
7
|
+
this.command = command;
|
|
8
|
+
this.intent = intent;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
let nextId = 1;
|
|
12
|
+
let interactive = false;
|
|
13
|
+
let approveAll = false;
|
|
14
|
+
const approvedIntents = new Set();
|
|
15
|
+
const queue = [];
|
|
16
|
+
const inFlightByCommand = new Map();
|
|
17
|
+
const listeners = new Set();
|
|
18
|
+
let current = null;
|
|
19
|
+
function isTty() {
|
|
20
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
21
|
+
}
|
|
22
|
+
function shouldPrompt(req) {
|
|
23
|
+
if (!interactive || !isTty())
|
|
24
|
+
return false;
|
|
25
|
+
if (approveAll || approvedIntents.has(req.intent))
|
|
26
|
+
return false;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
function notify() {
|
|
30
|
+
for (const listener of listeners) {
|
|
31
|
+
listener();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function pumpQueue() {
|
|
35
|
+
if (current || queue.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
current = queue.shift() || null;
|
|
38
|
+
notify();
|
|
39
|
+
}
|
|
40
|
+
function complete(approval, decision) {
|
|
41
|
+
inFlightByCommand.delete(approval.command);
|
|
42
|
+
for (const resolve of approval.resolvers) {
|
|
43
|
+
resolve(decision);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function completeQueuedByIntent(intent) {
|
|
47
|
+
for (let i = queue.length - 1; i >= 0; i -= 1) {
|
|
48
|
+
const approval = queue[i];
|
|
49
|
+
if (approval.intent !== intent)
|
|
50
|
+
continue;
|
|
51
|
+
queue.splice(i, 1);
|
|
52
|
+
complete(approval, "approve");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function setCommandApprovalInteractive(value) {
|
|
56
|
+
interactive = value;
|
|
57
|
+
}
|
|
58
|
+
export function subscribeCommandApprovals(listener) {
|
|
59
|
+
listeners.add(listener);
|
|
60
|
+
return () => {
|
|
61
|
+
listeners.delete(listener);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function getCurrentCommandApproval() {
|
|
65
|
+
if (!current)
|
|
66
|
+
return null;
|
|
67
|
+
const { resolvers: _resolvers, ...pending } = current;
|
|
68
|
+
return pending;
|
|
69
|
+
}
|
|
70
|
+
export function respondToCommandApproval(id, decision, scope = "once") {
|
|
71
|
+
if (!current || current.id !== id)
|
|
72
|
+
return;
|
|
73
|
+
const approval = current;
|
|
74
|
+
current = null;
|
|
75
|
+
if (decision === "approve") {
|
|
76
|
+
if (scope === "all") {
|
|
77
|
+
approveAll = true;
|
|
78
|
+
}
|
|
79
|
+
else if (scope === "all-like") {
|
|
80
|
+
approvedIntents.add(approval.intent);
|
|
81
|
+
completeQueuedByIntent(approval.intent);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
complete(approval, decision);
|
|
85
|
+
notify();
|
|
86
|
+
pumpQueue();
|
|
87
|
+
}
|
|
88
|
+
export async function requestCommandApproval(req) {
|
|
89
|
+
if (!shouldPrompt(req)) {
|
|
90
|
+
return "approve";
|
|
91
|
+
}
|
|
92
|
+
const existing = inFlightByCommand.get(req.command);
|
|
93
|
+
if (existing) {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
existing.resolvers.push(resolve);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
const approval = {
|
|
100
|
+
id: nextId++,
|
|
101
|
+
...req,
|
|
102
|
+
resolvers: [resolve],
|
|
103
|
+
};
|
|
104
|
+
inFlightByCommand.set(req.command, approval);
|
|
105
|
+
queue.push(approval);
|
|
106
|
+
pumpQueue();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export async function approveCloudCommandOrThrow(req) {
|
|
110
|
+
const decision = await requestCommandApproval(req);
|
|
111
|
+
if (decision === "deny") {
|
|
112
|
+
throw new CommandDeniedError(req.command, req.intent);
|
|
113
|
+
}
|
|
114
|
+
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -15,6 +15,16 @@ export declare function listDeployments(): Promise<string[]>;
|
|
|
15
15
|
* Checks if a deployment exists
|
|
16
16
|
*/
|
|
17
17
|
export declare function deploymentExists(name: string): Promise<boolean>;
|
|
18
|
+
export declare function resolveDeploymentConfigVersion(parsed: Record<string, unknown>, values?: {
|
|
19
|
+
global?: {
|
|
20
|
+
version?: unknown;
|
|
21
|
+
};
|
|
22
|
+
}, state?: {
|
|
23
|
+
version?: unknown;
|
|
24
|
+
application?: {
|
|
25
|
+
version?: unknown;
|
|
26
|
+
};
|
|
27
|
+
}): string;
|
|
18
28
|
/**
|
|
19
29
|
* Saves a deployment configuration
|
|
20
30
|
*/
|
|
@@ -24,8 +34,8 @@ export declare function saveDeploymentConfig(config: DeploymentConfig): Promise<
|
|
|
24
34
|
*/
|
|
25
35
|
export declare function loadDeploymentConfig(name: string): Promise<DeploymentConfig>;
|
|
26
36
|
/**
|
|
27
|
-
* Clones a deployment configuration to a new name
|
|
28
|
-
* Only copies config.yaml with the new name - state
|
|
37
|
+
* Clones a deployment configuration to a new name.
|
|
38
|
+
* Only copies config.yaml with the new name - state is not copied.
|
|
29
39
|
*/
|
|
30
40
|
export declare function cloneDeploymentConfig(sourceName: string, targetName: string): Promise<DeploymentConfig>;
|
|
31
41
|
/**
|
|
@@ -48,14 +58,6 @@ export declare function loadHelmValues(name: string): Promise<Record<string, unk
|
|
|
48
58
|
* Gets the Helm values file path
|
|
49
59
|
*/
|
|
50
60
|
export declare function getHelmValuesPath(name: string): string;
|
|
51
|
-
/**
|
|
52
|
-
* Saves Terraform variables
|
|
53
|
-
*/
|
|
54
|
-
export declare function saveTerraformVars(name: string, vars: Record<string, unknown>): Promise<string>;
|
|
55
|
-
/**
|
|
56
|
-
* Gets the Terraform working directory
|
|
57
|
-
*/
|
|
58
|
-
export declare function getTerraformDir(name: string): string;
|
|
59
61
|
/**
|
|
60
62
|
* Deletes a deployment and all its files
|
|
61
63
|
*/
|
package/dist/lib/config.js
CHANGED
|
@@ -44,6 +44,83 @@ export async function deploymentExists(name) {
|
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
function migrateStorageConfig(parsed) {
|
|
48
|
+
if (!parsed || typeof parsed !== "object")
|
|
49
|
+
return;
|
|
50
|
+
// Collapse the older per-purpose storage shape (separate decisionLogs/dbBackups
|
|
51
|
+
// buckets, containers, and identities) into a single bucket/container with key
|
|
52
|
+
// prefixes. The decision-logs location is treated as canonical; the dbBackups
|
|
53
|
+
// path becomes a prefix in the same bucket/container.
|
|
54
|
+
const storage = parsed.storage;
|
|
55
|
+
if (storage && typeof storage === "object" && storage.decisionLogs) {
|
|
56
|
+
const dl = storage.decisionLogs || {};
|
|
57
|
+
const db = storage.dbBackups || {};
|
|
58
|
+
parsed.storage = {
|
|
59
|
+
provider: storage.provider,
|
|
60
|
+
cloudAuthMode: storage.cloudAuthMode,
|
|
61
|
+
bucket: dl.bucket ?? db.bucket,
|
|
62
|
+
region: dl.region ?? db.region,
|
|
63
|
+
awsIamRoleArn: storage.awsIamRoleArn,
|
|
64
|
+
azureBlobClientId: storage.azureBlobClientId,
|
|
65
|
+
azureBlobTenantId: storage.azureBlobTenantId,
|
|
66
|
+
azureBlobConnectionStringSecretRef: storage.azureBlobConnectionStringSecretRef,
|
|
67
|
+
azureBlobContainer: dl.azureBlobContainer ?? db.azureBlobContainer,
|
|
68
|
+
gcpServiceAccountEmail: storage.gcpServiceAccountEmail,
|
|
69
|
+
paths: {
|
|
70
|
+
decisionLogs: dl.path || "decision-logs",
|
|
71
|
+
dbBackups: db.path || "db-backups",
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (parsed.features?.decisionLogQuery) {
|
|
76
|
+
delete parsed.features.decisionLogQuery;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function resolveDeploymentConfigVersion(parsed, values, state) {
|
|
80
|
+
if (typeof values?.global?.version === "string" && values.global.version) {
|
|
81
|
+
return values.global.version;
|
|
82
|
+
}
|
|
83
|
+
if (typeof state?.application?.version === "string" &&
|
|
84
|
+
state.application.version) {
|
|
85
|
+
return state.application.version;
|
|
86
|
+
}
|
|
87
|
+
if (typeof state?.version === "string" && state.version) {
|
|
88
|
+
return state.version;
|
|
89
|
+
}
|
|
90
|
+
if (typeof parsed.chartVersion === "string" && parsed.chartVersion) {
|
|
91
|
+
return parsed.chartVersion;
|
|
92
|
+
}
|
|
93
|
+
return "latest";
|
|
94
|
+
}
|
|
95
|
+
async function inferMissingVersion(name, parsed) {
|
|
96
|
+
const dir = getDeploymentDir(name);
|
|
97
|
+
let values;
|
|
98
|
+
let state;
|
|
99
|
+
try {
|
|
100
|
+
const valuesContent = await fs.readFile(path.join(dir, "values.yaml"), "utf-8");
|
|
101
|
+
values = yaml.parse(valuesContent);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// values.yaml is optional for config-only deployments.
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const stateContent = await fs.readFile(path.join(dir, "state.yaml"), "utf-8");
|
|
108
|
+
state = yaml.parse(stateContent);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// state.yaml may not exist yet.
|
|
112
|
+
}
|
|
113
|
+
return resolveDeploymentConfigVersion(parsed, values, state);
|
|
114
|
+
}
|
|
115
|
+
async function migrateConfig(name, parsed) {
|
|
116
|
+
if (!parsed || typeof parsed !== "object")
|
|
117
|
+
return;
|
|
118
|
+
const config = parsed;
|
|
119
|
+
migrateStorageConfig(config);
|
|
120
|
+
if (typeof config.version !== "string" || !config.version) {
|
|
121
|
+
config.version = await inferMissingVersion(name, config);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
47
124
|
/**
|
|
48
125
|
* Saves a deployment configuration
|
|
49
126
|
*/
|
|
@@ -60,11 +137,21 @@ export async function loadDeploymentConfig(name) {
|
|
|
60
137
|
const configPath = path.join(getDeploymentDir(name), "config.yaml");
|
|
61
138
|
const content = await fs.readFile(configPath, "utf-8");
|
|
62
139
|
const parsed = yaml.parse(content);
|
|
140
|
+
if (parsed &&
|
|
141
|
+
typeof parsed === "object" &&
|
|
142
|
+
"infrastructure" in parsed &&
|
|
143
|
+
parsed.infrastructure &&
|
|
144
|
+
typeof parsed.infrastructure === "object" &&
|
|
145
|
+
"mode" in parsed.infrastructure &&
|
|
146
|
+
parsed.infrastructure.mode === "provision") {
|
|
147
|
+
throw new Error(`Deployment "${name}" was created with CLI-managed infrastructure, which is no longer supported. Use an existing Kubernetes cluster and create a new deployment config.`);
|
|
148
|
+
}
|
|
149
|
+
await migrateConfig(name, parsed);
|
|
63
150
|
return DeploymentConfigSchema.parse(parsed);
|
|
64
151
|
}
|
|
65
152
|
/**
|
|
66
|
-
* Clones a deployment configuration to a new name
|
|
67
|
-
* Only copies config.yaml with the new name - state
|
|
153
|
+
* Clones a deployment configuration to a new name.
|
|
154
|
+
* Only copies config.yaml with the new name - state is not copied.
|
|
68
155
|
*/
|
|
69
156
|
export async function cloneDeploymentConfig(sourceName, targetName) {
|
|
70
157
|
const sourceConfig = await loadDeploymentConfig(sourceName);
|
|
@@ -126,35 +213,6 @@ export async function loadHelmValues(name) {
|
|
|
126
213
|
export function getHelmValuesPath(name) {
|
|
127
214
|
return path.join(getDeploymentDir(name), "values.yaml");
|
|
128
215
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Saves Terraform variables
|
|
131
|
-
*/
|
|
132
|
-
export async function saveTerraformVars(name, vars) {
|
|
133
|
-
const dir = path.join(getDeploymentDir(name), "terraform");
|
|
134
|
-
await fs.mkdir(dir, { recursive: true });
|
|
135
|
-
// Convert to HCL-compatible format
|
|
136
|
-
let content = "";
|
|
137
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
138
|
-
if (typeof value === "string") {
|
|
139
|
-
content += `${key} = "${value}"\n`;
|
|
140
|
-
}
|
|
141
|
-
else if (typeof value === "number" || typeof value === "boolean") {
|
|
142
|
-
content += `${key} = ${value}\n`;
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
content += `${key} = ${JSON.stringify(value)}\n`;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const varsPath = path.join(dir, "terraform.tfvars");
|
|
149
|
-
await fs.writeFile(varsPath, content, "utf-8");
|
|
150
|
-
return varsPath;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Gets the Terraform working directory
|
|
154
|
-
*/
|
|
155
|
-
export function getTerraformDir(name) {
|
|
156
|
-
return path.join(getDeploymentDir(name), "terraform");
|
|
157
|
-
}
|
|
158
216
|
/**
|
|
159
217
|
* Deletes a deployment and all its files
|
|
160
218
|
*/
|
|
@@ -212,6 +270,7 @@ export async function loadProfile() {
|
|
|
212
270
|
try {
|
|
213
271
|
const content = await fs.readFile(profilePath, "utf-8");
|
|
214
272
|
const data = yaml.parse(content);
|
|
273
|
+
migrateStorageConfig(data);
|
|
215
274
|
return ProfileConfigSchema.parse(data);
|
|
216
275
|
}
|
|
217
276
|
catch {
|
|
@@ -238,7 +297,6 @@ export function extractProfileFromConfig(config) {
|
|
|
238
297
|
provider: config.infrastructure.provider,
|
|
239
298
|
region: config.infrastructure.region,
|
|
240
299
|
clusterName: config.infrastructure.clusterName,
|
|
241
|
-
infrastructureMode: config.infrastructure.mode,
|
|
242
300
|
// Domain - store suffix for suggesting new domains
|
|
243
301
|
domainSuffix: extractDomainSuffix(config.domain),
|
|
244
302
|
adminEmail: config.adminEmail,
|
|
@@ -255,8 +313,8 @@ export function extractProfileFromConfig(config) {
|
|
|
255
313
|
openaiApiKey: config.features.ai.openaiApiKey,
|
|
256
314
|
licenseKey: config.licenseKey,
|
|
257
315
|
// Preferences
|
|
258
|
-
tier: config.tier,
|
|
259
316
|
databaseType: config.database.type,
|
|
317
|
+
storage: config.storage,
|
|
260
318
|
// SSO
|
|
261
319
|
ssoProvider: config.features.sso.provider,
|
|
262
320
|
ssoUrl: config.features.sso.url,
|