@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
|
@@ -1,40 +1,33 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from "react";
|
|
3
|
-
import { Box, Text
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
4
|
import SelectInput from "ink-select-input";
|
|
5
5
|
import TextInput from "ink-text-input";
|
|
6
6
|
import { useWizard } from "../WizardContext.js";
|
|
7
|
-
import { BorderBox, useTheme } from "../../common/index.js";
|
|
7
|
+
import { BorderBox, useGatedInput, useTheme } from "../../common/index.js";
|
|
8
8
|
import { Spinner } from "../../common/Spinner.js";
|
|
9
|
-
import { CLOUD_REGIONS } from "../../../types/index.js";
|
|
10
|
-
import { checkAllCloudClis,
|
|
9
|
+
import { CLOUD_PROVIDER_NAMES, CLOUD_REGIONS, } from "../../../types/index.js";
|
|
10
|
+
import { checkAllCloudClis, discoverClustersInRegion, listRegions, getGcpProjectId, updateKubeconfig, CLI_INSTALL_URLS, CLI_LOGIN_COMMANDS, } from "../../../lib/cloudCli.js";
|
|
11
11
|
export function CloudProviderStep({ onComplete, onBack, }) {
|
|
12
12
|
const { state, dispatch } = useWizard();
|
|
13
13
|
const { colors } = useTheme();
|
|
14
14
|
const [subStep, setSubStep] = useState("checking");
|
|
15
15
|
const [cliStatus, setCliStatus] = useState(null);
|
|
16
|
-
const [terraformStatus, setTerraformStatus] = useState(null);
|
|
17
16
|
const [clusterName, setClusterName] = useState(state.clusterName || "rulebricks-cluster");
|
|
18
|
-
const [gcpProject, setGcpProject] = useState(state.gcpProjectId || "");
|
|
19
|
-
const [azureRg, setAzureRg] = useState(state.azureResourceGroup || "");
|
|
20
|
-
const [regions, setRegions] = useState([]);
|
|
21
|
-
const [regionsLoading, setRegionsLoading] = useState(false);
|
|
22
17
|
const [clusters, setClusters] = useState([]);
|
|
23
|
-
const [
|
|
24
|
-
//
|
|
25
|
-
|
|
18
|
+
const [selectedProvider, setSelectedProvider] = useState(state.provider);
|
|
19
|
+
// Manual-entry path (no clusters discovered): region and, on Azure, the
|
|
20
|
+
// resource group still need to be captured so downstream steps (kubeconfig,
|
|
21
|
+
// storage/monitoring discovery) aren't left blind.
|
|
22
|
+
const [manualRegion, setManualRegion] = useState(state.region || "");
|
|
23
|
+
const [manualRegions, setManualRegions] = useState([]);
|
|
24
|
+
const [manualResourceGroup, setManualResourceGroup] = useState(state.azureResourceGroup || "");
|
|
25
|
+
const [rgError, setRgError] = useState(null);
|
|
26
26
|
// Check CLIs on mount
|
|
27
27
|
useEffect(() => {
|
|
28
28
|
async function checkClis() {
|
|
29
|
-
|
|
30
|
-
const [status, tfStatus] = await Promise.all([
|
|
31
|
-
checkAllCloudClis(),
|
|
32
|
-
needsTerraform
|
|
33
|
-
? checkTerraform()
|
|
34
|
-
: Promise.resolve({ installed: true }),
|
|
35
|
-
]);
|
|
29
|
+
const status = await checkAllCloudClis();
|
|
36
30
|
setCliStatus(status);
|
|
37
|
-
setTerraformStatus(tfStatus);
|
|
38
31
|
if (!status.anyInstalled) {
|
|
39
32
|
setSubStep("no-cli");
|
|
40
33
|
}
|
|
@@ -43,8 +36,8 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
43
36
|
}
|
|
44
37
|
}
|
|
45
38
|
checkClis();
|
|
46
|
-
}, [
|
|
47
|
-
|
|
39
|
+
}, []);
|
|
40
|
+
useGatedInput((input, key) => {
|
|
48
41
|
if (key.escape) {
|
|
49
42
|
if (subStep === "provider" ||
|
|
50
43
|
subStep === "no-cli" ||
|
|
@@ -54,17 +47,20 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
54
47
|
else if (subStep === "region" || subStep === "region-loading") {
|
|
55
48
|
setSubStep("provider");
|
|
56
49
|
}
|
|
50
|
+
else if (subStep === "manual-region" ||
|
|
51
|
+
subStep === "manual-region-loading") {
|
|
52
|
+
setSubStep("cluster");
|
|
53
|
+
}
|
|
54
|
+
else if (subStep === "manual-rg") {
|
|
55
|
+
setRgError(null);
|
|
56
|
+
setSubStep("manual-region");
|
|
57
|
+
}
|
|
57
58
|
else if (subStep === "cluster" ||
|
|
58
59
|
subStep === "cluster-loading" ||
|
|
59
|
-
subStep === "cluster-select"
|
|
60
|
+
subStep === "cluster-select" ||
|
|
61
|
+
subStep === "kubeconfig-loading") {
|
|
60
62
|
setSubStep("region");
|
|
61
63
|
}
|
|
62
|
-
else if (subStep === "gcp-project") {
|
|
63
|
-
setSubStep("provider");
|
|
64
|
-
}
|
|
65
|
-
else if (subStep === "azure-rg") {
|
|
66
|
-
setSubStep("provider");
|
|
67
|
-
}
|
|
68
64
|
}
|
|
69
65
|
});
|
|
70
66
|
// Build provider items with status
|
|
@@ -78,79 +74,37 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
78
74
|
];
|
|
79
75
|
return providers.map((p) => ({
|
|
80
76
|
...p,
|
|
81
|
-
|
|
82
|
-
// kubeconfig refresh details. Strict auth/quota checks are only required
|
|
83
|
-
// when the CLI will provision infrastructure.
|
|
84
|
-
disabled: needsTerraform
|
|
85
|
-
? !p.status.installed || !p.status.authenticated
|
|
86
|
-
: !p.status.installed,
|
|
77
|
+
disabled: !p.status.authenticated,
|
|
87
78
|
}));
|
|
88
79
|
};
|
|
89
80
|
const providerItems = getProviderItems();
|
|
90
|
-
const handleProviderSelect =
|
|
81
|
+
const handleProviderSelect = (item) => {
|
|
91
82
|
const selectedItem = providerItems.find((p) => p.value === item.value);
|
|
92
83
|
if (!selectedItem || selectedItem.disabled)
|
|
93
84
|
return;
|
|
94
85
|
const provider = item.value;
|
|
86
|
+
setSelectedProvider(provider);
|
|
87
|
+
setClusterName("rulebricks-cluster");
|
|
88
|
+
setManualRegion("");
|
|
89
|
+
setManualResourceGroup("");
|
|
95
90
|
dispatch({ type: "SET_PROVIDER", provider });
|
|
96
|
-
|
|
97
|
-
// GCP requires project to be pre-configured, so use the detected project
|
|
98
|
-
const detectedProject = await getGcpProjectId();
|
|
99
|
-
if (detectedProject) {
|
|
100
|
-
dispatch({ type: "SET_GCP_PROJECT", projectId: detectedProject });
|
|
101
|
-
// Skip project input and go directly to regions
|
|
102
|
-
loadRegions(provider);
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
// Fallback to project input if somehow not detected (shouldn't happen with new auth check)
|
|
106
|
-
setSubStep("gcp-project");
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else if (provider === "azure") {
|
|
110
|
-
setSubStep("azure-rg");
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
loadRegions(provider);
|
|
114
|
-
}
|
|
91
|
+
loadProviderRegions(provider);
|
|
115
92
|
};
|
|
116
|
-
const
|
|
93
|
+
const loadProviderRegions = async (provider) => {
|
|
117
94
|
setSubStep("region-loading");
|
|
118
|
-
setRegionsLoading(true);
|
|
119
95
|
try {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
setRegions(dynamicRegions);
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
// Fall back to static regions
|
|
126
|
-
setRegions(CLOUD_REGIONS[provider]);
|
|
127
|
-
}
|
|
96
|
+
const regions = await listRegions(provider);
|
|
97
|
+
setManualRegions(regions.length > 0 ? regions : CLOUD_REGIONS[provider]);
|
|
128
98
|
}
|
|
129
99
|
catch {
|
|
130
|
-
|
|
131
|
-
setRegions(CLOUD_REGIONS[provider]);
|
|
100
|
+
setManualRegions(CLOUD_REGIONS[provider]);
|
|
132
101
|
}
|
|
133
|
-
setRegionsLoading(false);
|
|
134
102
|
setSubStep("region");
|
|
135
103
|
};
|
|
136
|
-
const handleRegionSelect = (item) => {
|
|
137
|
-
dispatch({ type: "SET_REGION", region: item.value });
|
|
138
|
-
// For existing infrastructure, load available clusters
|
|
139
|
-
if (state.infrastructureMode === "existing" && state.provider) {
|
|
140
|
-
loadClusters(state.provider, item.value);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
// For provisioning, go directly to cluster name input
|
|
144
|
-
setSubStep("cluster");
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
104
|
const loadClusters = async (provider, region) => {
|
|
148
105
|
setSubStep("cluster-loading");
|
|
149
|
-
setClustersLoading(true);
|
|
150
106
|
try {
|
|
151
|
-
const availableClusters = await
|
|
152
|
-
azureResourceGroup: state.azureResourceGroup || undefined,
|
|
153
|
-
});
|
|
107
|
+
const availableClusters = await discoverClustersInRegion(provider, region);
|
|
154
108
|
setClusters(availableClusters);
|
|
155
109
|
if (availableClusters.length > 0) {
|
|
156
110
|
setSubStep("cluster-select");
|
|
@@ -165,27 +119,130 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
165
119
|
setClusters([]);
|
|
166
120
|
setSubStep("cluster");
|
|
167
121
|
}
|
|
168
|
-
setClustersLoading(false);
|
|
169
122
|
};
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
dispatch({ type: "SET_CLUSTER_NAME", clusterName:
|
|
123
|
+
const completeWithCluster = async (cluster) => {
|
|
124
|
+
dispatch({ type: "SET_REGION", region: cluster.region });
|
|
125
|
+
dispatch({ type: "SET_CLUSTER_NAME", clusterName: cluster.name });
|
|
126
|
+
dispatch({ type: "SET_AZURE_RG", resourceGroup: cluster.resourceGroup || "" });
|
|
127
|
+
dispatch({ type: "SET_GCP_PROJECT", projectId: cluster.projectId || "" });
|
|
128
|
+
if (cluster.provider && cluster.region) {
|
|
129
|
+
setSubStep("kubeconfig-loading");
|
|
130
|
+
try {
|
|
131
|
+
await updateKubeconfig(cluster.provider, cluster.name, cluster.region, {
|
|
132
|
+
gcpProjectId: cluster.projectId,
|
|
133
|
+
azureResourceGroup: cluster.resourceGroup,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// The next step performs a direct kubectl scan and will show a concrete
|
|
138
|
+
// access error if kubeconfig still points at the wrong cluster.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
173
141
|
onComplete();
|
|
174
142
|
};
|
|
143
|
+
const handleClusterSelect = (item) => {
|
|
144
|
+
const cluster = clusters.find((c) => getClusterKey(c) === item.value);
|
|
145
|
+
if (!cluster)
|
|
146
|
+
return;
|
|
147
|
+
setClusterName(cluster.name);
|
|
148
|
+
completeWithCluster(cluster);
|
|
149
|
+
};
|
|
175
150
|
const handleClusterSubmit = () => {
|
|
176
151
|
dispatch({ type: "SET_CLUSTER_NAME", clusterName });
|
|
177
|
-
|
|
152
|
+
const provider = selectedProvider || state.provider;
|
|
153
|
+
if (!manualRegion) {
|
|
154
|
+
loadManualRegions();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (provider === "azure" && !manualResourceGroup.trim()) {
|
|
158
|
+
setSubStep("manual-rg");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
completeManual(manualRegion, manualResourceGroup.trim());
|
|
162
|
+
};
|
|
163
|
+
// Manual path: the cluster wasn't discovered, so the region (and Azure
|
|
164
|
+
// resource group) are collected explicitly before refreshing kubeconfig
|
|
165
|
+
// otherwise the cluster capability scan runs against whatever kubectl happens
|
|
166
|
+
// to point at and storage/monitoring discovery start without a region.
|
|
167
|
+
const loadManualRegions = async () => {
|
|
168
|
+
const provider = selectedProvider || state.provider;
|
|
169
|
+
if (!provider) {
|
|
170
|
+
onComplete();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
setSubStep("manual-region-loading");
|
|
174
|
+
try {
|
|
175
|
+
const regions = await listRegions(provider);
|
|
176
|
+
setManualRegions(regions.length > 0 ? regions : CLOUD_REGIONS[provider]);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
setManualRegions(CLOUD_REGIONS[provider]);
|
|
180
|
+
}
|
|
181
|
+
setSubStep("manual-region");
|
|
182
|
+
};
|
|
183
|
+
const handleManualRegionSelect = (item) => {
|
|
184
|
+
setManualRegion(item.value);
|
|
185
|
+
dispatch({ type: "SET_REGION", region: item.value });
|
|
186
|
+
const provider = selectedProvider || state.provider;
|
|
187
|
+
if (provider === "azure") {
|
|
188
|
+
setSubStep("manual-rg");
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
completeManual(item.value, "");
|
|
192
|
+
}
|
|
178
193
|
};
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
194
|
+
const handleRegionSelect = (item) => {
|
|
195
|
+
const provider = selectedProvider || state.provider;
|
|
196
|
+
if (!provider)
|
|
197
|
+
return;
|
|
198
|
+
setManualRegion(item.value);
|
|
199
|
+
dispatch({ type: "SET_REGION", region: item.value });
|
|
200
|
+
loadClusters(provider, item.value);
|
|
201
|
+
};
|
|
202
|
+
const handleManualResourceGroupSubmit = () => {
|
|
203
|
+
if (!manualResourceGroup.trim()) {
|
|
204
|
+
setRgError("Resource group is required for AKS clusters");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
setRgError(null);
|
|
208
|
+
dispatch({
|
|
209
|
+
type: "SET_AZURE_RG",
|
|
210
|
+
resourceGroup: manualResourceGroup.trim(),
|
|
211
|
+
});
|
|
212
|
+
completeManual(manualRegion, manualResourceGroup.trim());
|
|
183
213
|
};
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
214
|
+
const completeManual = async (region, resourceGroup) => {
|
|
215
|
+
const provider = selectedProvider || state.provider;
|
|
216
|
+
if (!provider) {
|
|
217
|
+
onComplete();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
setSubStep("kubeconfig-loading");
|
|
221
|
+
// GCP: derive the project from the active gcloud config instead of asking.
|
|
222
|
+
let gcpProjectId;
|
|
223
|
+
if (provider === "gcp") {
|
|
224
|
+
try {
|
|
225
|
+
gcpProjectId = (await getGcpProjectId()) || undefined;
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
gcpProjectId = undefined;
|
|
229
|
+
}
|
|
230
|
+
if (gcpProjectId) {
|
|
231
|
+
dispatch({ type: "SET_GCP_PROJECT", projectId: gcpProjectId });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
await updateKubeconfig(provider, clusterName, region, {
|
|
236
|
+
gcpProjectId,
|
|
237
|
+
azureResourceGroup: resourceGroup || undefined,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// The next step performs a direct kubectl scan and will show a concrete
|
|
242
|
+
// access error if kubeconfig still points at the wrong cluster.
|
|
243
|
+
}
|
|
244
|
+
onComplete();
|
|
187
245
|
};
|
|
188
|
-
const regionItems = regions.map((r) => ({ label: r, value: r }));
|
|
189
246
|
// Render status indicator for a provider
|
|
190
247
|
const renderStatusIndicator = (status) => {
|
|
191
248
|
if (!status.installed) {
|
|
@@ -203,11 +260,15 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
203
260
|
}
|
|
204
261
|
return _jsx(Text, { color: "green", children: " \u2713" });
|
|
205
262
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
263
|
+
const clusterItems = clusters.map((cluster) => ({
|
|
264
|
+
label: formatClusterRow(cluster),
|
|
265
|
+
value: getClusterKey(cluster),
|
|
266
|
+
}));
|
|
267
|
+
const activeProvider = selectedProvider || state.provider;
|
|
268
|
+
const providerName = activeProvider
|
|
269
|
+
? CLOUD_PROVIDER_NAMES[activeProvider]
|
|
270
|
+
: "";
|
|
271
|
+
return (_jsxs(BorderBox, { title: "Cloud Provider", children: [subStep === "checking" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Spinner, { label: "Checking cloud CLI tools..." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Detecting AWS, GCP, and Azure CLIs..." }) })] })), subStep === "no-cli" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "red", bold: true, children: "No cloud CLI tools detected" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Text, { children: "To discover clusters, install at least one cloud CLI:" }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", marginLeft: 2, children: Object.entries(CLI_INSTALL_URLS).map(([provider, info]) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { bold: true, children: [info.name, ":"] }), _jsxs(Text, { color: "gray", children: [" ", info.installCmd] }), _jsxs(Text, { color: "gray", children: [" ", info.url] })] }, provider))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "After installing, authenticate:" }) }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: Object.entries(CLI_LOGIN_COMMANDS).map(([provider, cmd]) => (_jsx(Box, { flexDirection: "column", children: Array.isArray(cmd) ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "gray", children: [" ", provider, ":"] }), cmd.map((c, i) => (_jsxs(Text, { color: "gray", children: [" ", c] }, i)))] })) : (_jsxs(Text, { color: "gray", children: [" ", provider, ": ", cmd] })) }, provider))) })] })), subStep === "provider" && cliStatus && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your cloud provider:" }), !cliStatus.anyAvailable && cliStatus.anyInstalled && (_jsx(Text, { color: "yellow", dimColor: true, children: "Some CLIs are installed but not authenticated" }))] }), _jsx(SelectInput, { items: providerItems.map((p) => ({
|
|
211
272
|
label: p.label,
|
|
212
273
|
value: p.value,
|
|
213
274
|
})), onSelect: handleProviderSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
|
|
@@ -219,10 +280,34 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
219
280
|
: isSelected
|
|
220
281
|
? colors.accent
|
|
221
282
|
: undefined;
|
|
222
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: textColor, dimColor: item.disabled, children: [isSelected && !item.disabled ? "
|
|
223
|
-
} })] })), subStep === "
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
283
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: textColor, dimColor: item.disabled, children: [isSelected && !item.disabled ? "> " : " ", label] }), renderStatusIndicator(item.status), item.status.identity && item.status.authenticated && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", "(", item.status.identity, ")"] }))] }));
|
|
284
|
+
} })] })), subStep === "region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Loading available regions..." }) })), subStep === "region" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select the cluster's region:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Rulebricks will only search ", providerName, " clusters in this region."] }), _jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: manualRegions.map((r) => ({ label: r, value: r })), onSelect: handleRegionSelect, limit: 8, initialIndex: Math.max(0, manualRegions.indexOf(manualRegion)), indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "> " : " ", label] })) }) })] })), subStep === "cluster-loading" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Spinner, { label: `Fetching ${providerName} clusters in ${manualRegion}...` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "This keeps discovery targeted to the selected region." }) })] })), subStep === "cluster-select" && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select your Kubernetes cluster:" }), _jsxs(Text, { color: "gray", dimColor: true, children: [clusters.length, " cluster", clusters.length !== 1 ? "s" : "", " found for ", providerName, " in ", manualRegion] })] }), _jsx(Text, { color: "gray", dimColor: true, children: ` ${formatClusterColumns("Name", "Location", "Details", "Nodes")}` }), _jsx(Box, { height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: clusterItems, onSelect: handleClusterSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "> " : " ", label] })) }) })] })), subStep === "cluster" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the Kubernetes cluster name:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["No clusters were discovered for ", providerName, ". If kubectl already points at the cluster, you can enter its name manually."] }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Text, { color: "yellow", children: "Need a basic cluster? See cluster-setup/ for minimum Rulebricks examples." }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: " > " }), _jsx(TextInput, { value: clusterName, onChange: setClusterName, onSubmit: handleClusterSubmit, placeholder: "rulebricks-cluster" })] })] })), subStep === "manual-region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Loading available regions..." }) })), subStep === "manual-region" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Select the cluster's region:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Used to refresh kubeconfig and discover storage and monitoring resources for ", clusterName, "."] }), _jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: manualRegions.map((r) => ({ label: r, value: r })), onSelect: handleManualRegionSelect, limit: 8, initialIndex: Math.max(0, manualRegions.indexOf(manualRegion)), indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "> " : " ", label] })) }) })] })), subStep === "manual-rg" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the cluster's resource group:" }), _jsx(Text, { color: "gray", dimColor: true, children: "The Azure resource group containing the AKS cluster (needed for kubeconfig access)." }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.accent, children: " > " }), _jsx(TextInput, { value: manualResourceGroup, onChange: setManualResourceGroup, onSubmit: handleManualResourceGroupSubmit, placeholder: "my-resource-group" })] }), rgError && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", rgError] }) }))] })), subStep === "kubeconfig-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Refreshing kubeconfig for selected cluster..." }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
|
|
285
|
+
}
|
|
286
|
+
function getClusterKey(cluster) {
|
|
287
|
+
return [
|
|
288
|
+
cluster.provider,
|
|
289
|
+
cluster.region,
|
|
290
|
+
cluster.resourceGroup || cluster.projectId || "",
|
|
291
|
+
cluster.name,
|
|
292
|
+
].join(":");
|
|
293
|
+
}
|
|
294
|
+
function formatClusterRow(cluster) {
|
|
295
|
+
const details = cluster.resourceGroup || cluster.projectId || cluster.version || "-";
|
|
296
|
+
const nodes = cluster.nodeCount === undefined || cluster.nodeCount === null
|
|
297
|
+
? "-"
|
|
298
|
+
: String(cluster.nodeCount);
|
|
299
|
+
return formatClusterColumns(cluster.name, cluster.region, details, nodes);
|
|
300
|
+
}
|
|
301
|
+
function formatClusterColumns(name, location, details, nodes) {
|
|
302
|
+
return [
|
|
303
|
+
fit(name, 26),
|
|
304
|
+
fit(location, 16),
|
|
305
|
+
fit(details, 24),
|
|
306
|
+
fit(nodes, 5),
|
|
307
|
+
].join(" ");
|
|
308
|
+
}
|
|
309
|
+
function fit(value, width) {
|
|
310
|
+
const text = value || "-";
|
|
311
|
+
const clipped = text.length > width ? `${text.slice(0, Math.max(width - 1, 0))}~` : text;
|
|
312
|
+
return clipped.padEnd(width, " ");
|
|
228
313
|
}
|
|
@@ -25,7 +25,6 @@ export function DomainStep({ onComplete, onBack }) {
|
|
|
25
25
|
const [subStep, setSubStep] = useState('domain');
|
|
26
26
|
const [domain, setDomain] = useState(state.domain || '');
|
|
27
27
|
const [adminEmail, setAdminEmail] = useState(state.adminEmail || '');
|
|
28
|
-
const [tlsEmail, setTlsEmail] = useState(state.tlsEmail || '');
|
|
29
28
|
const [error, setError] = useState(null);
|
|
30
29
|
useInput((input, key) => {
|
|
31
30
|
if (key.escape) {
|
|
@@ -36,11 +35,8 @@ export function DomainStep({ onComplete, onBack }) {
|
|
|
36
35
|
else if (subStep === 'admin-email') {
|
|
37
36
|
setSubStep('domain');
|
|
38
37
|
}
|
|
39
|
-
else if (subStep === 'tls-email') {
|
|
40
|
-
setSubStep('admin-email');
|
|
41
|
-
}
|
|
42
38
|
else if (subStep === 'dns-provider') {
|
|
43
|
-
setSubStep('
|
|
39
|
+
setSubStep('admin-email');
|
|
44
40
|
}
|
|
45
41
|
else if (subStep === 'dns-auto-manage') {
|
|
46
42
|
setSubStep('dns-provider');
|
|
@@ -85,23 +81,8 @@ export function DomainStep({ onComplete, onBack }) {
|
|
|
85
81
|
}
|
|
86
82
|
setError(null);
|
|
87
83
|
dispatch({ type: 'SET_ADMIN_EMAIL', email: adminEmail });
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
setTlsEmail(adminEmail);
|
|
91
|
-
}
|
|
92
|
-
setSubStep('tls-email');
|
|
93
|
-
};
|
|
94
|
-
const handleTlsEmailSubmit = () => {
|
|
95
|
-
if (!tlsEmail) {
|
|
96
|
-
setError('TLS email is required');
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (!isValidEmail(tlsEmail)) {
|
|
100
|
-
setError('Invalid email format');
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
setError(null);
|
|
104
|
-
dispatch({ type: 'SET_TLS_EMAIL', email: tlsEmail });
|
|
84
|
+
// The TLS (Let's Encrypt) email defaults to the admin email in toConfig;
|
|
85
|
+
// advanced users can override `tlsEmail` in config.yaml.
|
|
105
86
|
setSubStep('dns-provider');
|
|
106
87
|
};
|
|
107
88
|
const handleDnsProviderSelect = (item) => {
|
|
@@ -121,6 +102,6 @@ export function DomainStep({ onComplete, onBack }) {
|
|
|
121
102
|
onComplete();
|
|
122
103
|
};
|
|
123
104
|
// Progress summary component
|
|
124
|
-
const ProgressSummary = () => (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [domain && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Domain: ", domain] })] })), adminEmail && subStep !== 'admin-email' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Admin: ", adminEmail] })] }))
|
|
125
|
-
return (_jsxs(BorderBox, { title: "Domain & DNS", children: [subStep === 'domain' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Rulebricks domain:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This is where Rulebricks will be accessible (e.g., rulebricks.example.com)" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: domain, onChange: setDomain, onSubmit: handleDomainSubmit, placeholder: "rulebricks.example.com" }) }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) }))] })), subStep === 'validating' && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Validating domain..." }) })), subStep === 'admin-email' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the admin email address:" }), _jsx(Text, { color: "gray", dimColor: true, children: "
|
|
105
|
+
const ProgressSummary = () => (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [domain && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Domain: ", domain] })] })), adminEmail && subStep !== 'admin-email' && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Admin: ", adminEmail] })] }))] }));
|
|
106
|
+
return (_jsxs(BorderBox, { title: "Domain & DNS", children: [subStep === 'domain' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter your Rulebricks domain:" }), _jsx(Text, { color: "gray", dimColor: true, children: "This is where Rulebricks will be accessible (e.g., rulebricks.example.com)" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: domain, onChange: setDomain, onSubmit: handleDomainSubmit, placeholder: "rulebricks.example.com" }) }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) }))] })), subStep === 'validating' && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Validating domain..." }) })), subStep === 'admin-email' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Enter the admin email address:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Used for Rulebricks administration, notifications, and TLS certificate (Let's Encrypt) notices" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: adminEmail, onChange: setAdminEmail, onSubmit: handleAdminEmailSubmit, placeholder: "admin@example.com" }) }), _jsx(ProgressSummary, {}), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) }))] })), subStep === 'dns-provider' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Where is your domain's DNS hosted?" }), _jsx(Text, { color: "gray", dimColor: true, children: "This determines whether we can automatically manage DNS records for you" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: DNS_PROVIDER_OPTIONS, onSelect: handleDnsProviderSelect, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) }) }), _jsx(ProgressSummary, {})] })), subStep === 'dns-auto-manage' && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Automatic DNS Management" }), _jsx(Text, { color: "gray", dimColor: true, children: "Would you like Rulebricks to automatically create and manage DNS records?" }), _jsx(Text, { color: "gray", dimColor: true, children: "This enables single-step deployment without manual DNS configuration." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: AUTO_MANAGE_OPTIONS, onSelect: handleAutoManageSelect, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) }) }), _jsx(ProgressSummary, {}), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" DNS Provider: ", DNS_PROVIDER_NAMES[state.dnsProvider]] })] }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: _jsx(Text, { color: "yellow", children: "Note: Auto-DNS requires external-dns with proper IAM credentials in your cluster." }) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
|
|
126
107
|
}
|