@rulebricks/cli 2.1.6 → 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 +75 -14
- package/cluster-setup/aws/README.md +123 -0
- package/cluster-setup/aws/check-aws-access.sh +242 -0
- package/cluster-setup/aws/parameters.json +13 -0
- package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
- package/cluster-setup/azure/README.md +141 -0
- package/cluster-setup/azure/check-aks-prereqs.sh +276 -0
- package/cluster-setup/azure/parameters.json +30 -0
- package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
- package/cluster-setup/gcp/README.md +189 -0
- package/cluster-setup/gcp/check-gke-prereqs.sh +260 -0
- 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 -47
- 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 +174 -29
- package/dist/components/Wizard/WizardContext.js +896 -91
- package/dist/components/Wizard/steps/CloudProviderStep.js +192 -102
- 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 +959 -248
- 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 -7
- 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 +1937 -259
- 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 +126 -13
- package/dist/lib/kubernetes.js +624 -134
- 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 +2152 -95
- package/dist/types/index.js +554 -286
- package/package.json +10 -4
- package/schema/values.schema.json +1934 -0
- 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,74 +74,37 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
78
74
|
];
|
|
79
75
|
return providers.map((p) => ({
|
|
80
76
|
...p,
|
|
81
|
-
disabled: !p.status.
|
|
77
|
+
disabled: !p.status.authenticated,
|
|
82
78
|
}));
|
|
83
79
|
};
|
|
84
80
|
const providerItems = getProviderItems();
|
|
85
|
-
const handleProviderSelect =
|
|
81
|
+
const handleProviderSelect = (item) => {
|
|
86
82
|
const selectedItem = providerItems.find((p) => p.value === item.value);
|
|
87
83
|
if (!selectedItem || selectedItem.disabled)
|
|
88
84
|
return;
|
|
89
85
|
const provider = item.value;
|
|
86
|
+
setSelectedProvider(provider);
|
|
87
|
+
setClusterName("rulebricks-cluster");
|
|
88
|
+
setManualRegion("");
|
|
89
|
+
setManualResourceGroup("");
|
|
90
90
|
dispatch({ type: "SET_PROVIDER", provider });
|
|
91
|
-
|
|
92
|
-
// GCP requires project to be pre-configured, so use the detected project
|
|
93
|
-
const detectedProject = await getGcpProjectId();
|
|
94
|
-
if (detectedProject) {
|
|
95
|
-
dispatch({ type: "SET_GCP_PROJECT", projectId: detectedProject });
|
|
96
|
-
// Skip project input and go directly to regions
|
|
97
|
-
loadRegions(provider);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
// Fallback to project input if somehow not detected (shouldn't happen with new auth check)
|
|
101
|
-
setSubStep("gcp-project");
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
else if (provider === "azure") {
|
|
105
|
-
setSubStep("azure-rg");
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
loadRegions(provider);
|
|
109
|
-
}
|
|
91
|
+
loadProviderRegions(provider);
|
|
110
92
|
};
|
|
111
|
-
const
|
|
93
|
+
const loadProviderRegions = async (provider) => {
|
|
112
94
|
setSubStep("region-loading");
|
|
113
|
-
setRegionsLoading(true);
|
|
114
95
|
try {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
setRegions(dynamicRegions);
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
// Fall back to static regions
|
|
121
|
-
setRegions(CLOUD_REGIONS[provider]);
|
|
122
|
-
}
|
|
96
|
+
const regions = await listRegions(provider);
|
|
97
|
+
setManualRegions(regions.length > 0 ? regions : CLOUD_REGIONS[provider]);
|
|
123
98
|
}
|
|
124
99
|
catch {
|
|
125
|
-
|
|
126
|
-
setRegions(CLOUD_REGIONS[provider]);
|
|
100
|
+
setManualRegions(CLOUD_REGIONS[provider]);
|
|
127
101
|
}
|
|
128
|
-
setRegionsLoading(false);
|
|
129
102
|
setSubStep("region");
|
|
130
103
|
};
|
|
131
|
-
const handleRegionSelect = (item) => {
|
|
132
|
-
dispatch({ type: "SET_REGION", region: item.value });
|
|
133
|
-
// For existing infrastructure, load available clusters
|
|
134
|
-
if (state.infrastructureMode === "existing" && state.provider) {
|
|
135
|
-
loadClusters(state.provider, item.value);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
// For provisioning, go directly to cluster name input
|
|
139
|
-
setSubStep("cluster");
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
104
|
const loadClusters = async (provider, region) => {
|
|
143
105
|
setSubStep("cluster-loading");
|
|
144
|
-
setClustersLoading(true);
|
|
145
106
|
try {
|
|
146
|
-
const availableClusters = await
|
|
147
|
-
azureResourceGroup: state.azureResourceGroup || undefined,
|
|
148
|
-
});
|
|
107
|
+
const availableClusters = await discoverClustersInRegion(provider, region);
|
|
149
108
|
setClusters(availableClusters);
|
|
150
109
|
if (availableClusters.length > 0) {
|
|
151
110
|
setSubStep("cluster-select");
|
|
@@ -160,27 +119,130 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
160
119
|
setClusters([]);
|
|
161
120
|
setSubStep("cluster");
|
|
162
121
|
}
|
|
163
|
-
setClustersLoading(false);
|
|
164
122
|
};
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
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
|
+
}
|
|
168
141
|
onComplete();
|
|
169
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
|
+
};
|
|
170
150
|
const handleClusterSubmit = () => {
|
|
171
151
|
dispatch({ type: "SET_CLUSTER_NAME", clusterName });
|
|
172
|
-
|
|
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
|
+
}
|
|
173
193
|
};
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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());
|
|
178
213
|
};
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
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();
|
|
182
245
|
};
|
|
183
|
-
const regionItems = regions.map((r) => ({ label: r, value: r }));
|
|
184
246
|
// Render status indicator for a provider
|
|
185
247
|
const renderStatusIndicator = (status) => {
|
|
186
248
|
if (!status.installed) {
|
|
@@ -198,11 +260,15 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
198
260
|
}
|
|
199
261
|
return _jsx(Text, { color: "green", children: " \u2713" });
|
|
200
262
|
};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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) => ({
|
|
206
272
|
label: p.label,
|
|
207
273
|
value: p.value,
|
|
208
274
|
})), onSelect: handleProviderSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => {
|
|
@@ -214,10 +280,34 @@ export function CloudProviderStep({ onComplete, onBack, }) {
|
|
|
214
280
|
: isSelected
|
|
215
281
|
? colors.accent
|
|
216
282
|
: undefined;
|
|
217
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: textColor, dimColor: item.disabled, children: [isSelected && !item.disabled ? "
|
|
218
|
-
} })] })), subStep === "
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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, " ");
|
|
223
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
|
}
|