@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
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import TextInput from "ink-text-input";
|
|
5
|
+
import SelectInput from "ink-select-input";
|
|
6
|
+
import { useWizard } from "../WizardContext.js";
|
|
7
|
+
import { BorderBox, useTheme } from "../../common/index.js";
|
|
8
|
+
const MODE_OPTIONS = [
|
|
9
|
+
{
|
|
10
|
+
label: "Prefer dedicated services for Rulebricks (recommended)",
|
|
11
|
+
value: "dedicated",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
label: "Connect to existing providers",
|
|
15
|
+
value: "existing",
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
const yesNo = (yesLabel, noLabel) => [
|
|
19
|
+
{ label: noLabel, value: "no" },
|
|
20
|
+
{ label: yesLabel, value: "yes" },
|
|
21
|
+
];
|
|
22
|
+
const PRESETS = [
|
|
23
|
+
{
|
|
24
|
+
id: "aws-msk-iam",
|
|
25
|
+
label: "AWS MSK (IAM auth)",
|
|
26
|
+
hint: "Pod identity (IRSA). Vector uses a kafka-proxy bridge sidecar.",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "azure-event-hubs",
|
|
30
|
+
label: "Azure Event Hubs",
|
|
31
|
+
hint: "SASL PLAIN with the namespace connection string.",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "gcp-managed",
|
|
35
|
+
label: "GCP Managed Service for Apache Kafka",
|
|
36
|
+
hint: "SASL PLAIN credentials (Vector cannot use GCP OAUTHBEARER).",
|
|
37
|
+
},
|
|
38
|
+
{ id: "custom", label: "Custom / other broker", hint: "Manual SSL + SASL." },
|
|
39
|
+
];
|
|
40
|
+
const CUSTOM_MECH = [
|
|
41
|
+
{ id: "", label: "None (SSL only)" },
|
|
42
|
+
{ id: "plain", label: "SASL PLAIN" },
|
|
43
|
+
{ id: "scram-sha-256", label: "SCRAM-SHA-256" },
|
|
44
|
+
{ id: "scram-sha-512", label: "SCRAM-SHA-512" },
|
|
45
|
+
];
|
|
46
|
+
const PG_INPUT_MODES = [
|
|
47
|
+
{
|
|
48
|
+
label: "Enter connection details (AWS RDS / Azure)",
|
|
49
|
+
value: "structured",
|
|
50
|
+
},
|
|
51
|
+
{ label: "Paste a Postgres connection string", value: "connstring" },
|
|
52
|
+
];
|
|
53
|
+
function defaultPresetForCloud(provider) {
|
|
54
|
+
if (provider === "aws")
|
|
55
|
+
return "aws-msk-iam";
|
|
56
|
+
if (provider === "azure")
|
|
57
|
+
return "azure-event-hubs";
|
|
58
|
+
if (provider === "gcp")
|
|
59
|
+
return "gcp-managed";
|
|
60
|
+
return "custom";
|
|
61
|
+
}
|
|
62
|
+
// Parse a postgres:// URL into parts. Returns null when it isn't parseable.
|
|
63
|
+
function parsePostgresUrl(raw) {
|
|
64
|
+
const trimmed = raw.trim();
|
|
65
|
+
if (!/^postgres(ql)?:\/\//i.test(trimmed))
|
|
66
|
+
return null;
|
|
67
|
+
try {
|
|
68
|
+
const u = new URL(trimmed.replace(/^postgres(ql)?:\/\//i, "http://"));
|
|
69
|
+
const db = u.pathname.replace(/^\//, "");
|
|
70
|
+
return {
|
|
71
|
+
host: u.hostname || undefined,
|
|
72
|
+
port: u.port ? Number.parseInt(u.port, 10) : undefined,
|
|
73
|
+
database: db || undefined,
|
|
74
|
+
user: u.username ? decodeURIComponent(u.username) : undefined,
|
|
75
|
+
password: u.password ? decodeURIComponent(u.password) : undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const SERVICE_ORDER = ["redis", "kafka", "postgres"];
|
|
83
|
+
export function ExternalServicesStep({ onComplete, onBack, }) {
|
|
84
|
+
const { state, dispatch } = useWizard();
|
|
85
|
+
const { colors } = useTheme();
|
|
86
|
+
const [field, setField] = useState("mode");
|
|
87
|
+
const [error, setError] = useState(null);
|
|
88
|
+
// Postgres external is only offered for self-hosted Supabase (there is no
|
|
89
|
+
// in-cluster database to externalize with Supabase Cloud) on providers we
|
|
90
|
+
// support a managed flow for.
|
|
91
|
+
const pgAvailable = (state.provider === "aws" || state.provider === "azure") &&
|
|
92
|
+
state.databaseType === "self-hosted";
|
|
93
|
+
// Multi-select of which services to connect to existing/managed providers.
|
|
94
|
+
const [selected, setSelected] = useState({
|
|
95
|
+
redis: state.redisMode === "external",
|
|
96
|
+
kafka: state.kafkaMode === "external",
|
|
97
|
+
postgres: pgAvailable && state.postgresMode === "external",
|
|
98
|
+
});
|
|
99
|
+
const whichItems = [
|
|
100
|
+
{ key: "redis", label: "Redis", hint: "Managed cache (ElastiCache, etc.)" },
|
|
101
|
+
{ key: "kafka", label: "Kafka", hint: "Managed event streaming (MSK, Event Hubs, etc.)" },
|
|
102
|
+
...(pgAvailable
|
|
103
|
+
? [
|
|
104
|
+
{
|
|
105
|
+
key: "postgres",
|
|
106
|
+
label: "Postgres database",
|
|
107
|
+
hint: state.provider === "aws"
|
|
108
|
+
? "Managed RDS / Aurora for the Supabase database."
|
|
109
|
+
: "Azure Flexible Server for the Supabase database.",
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
: []),
|
|
113
|
+
];
|
|
114
|
+
const [whichIndex, setWhichIndex] = useState(0);
|
|
115
|
+
// Redis
|
|
116
|
+
const [redisHost, setRedisHost] = useState(state.redisHost);
|
|
117
|
+
const [redisPort, setRedisPort] = useState(String(state.redisPort || 6379));
|
|
118
|
+
const [redisTls, setRedisTls] = useState(state.redisTls);
|
|
119
|
+
const [redisPassword, setRedisPassword] = useState(state.redisPassword);
|
|
120
|
+
const [redisExistingSecret, setRedisExistingSecret] = useState(state.redisExistingSecret);
|
|
121
|
+
// Kafka
|
|
122
|
+
const initialPreset = state.kafkaPreset ?? defaultPresetForCloud(state.provider);
|
|
123
|
+
const [presetIndex, setPresetIndex] = useState(Math.max(0, PRESETS.findIndex((p) => p.id === initialPreset)));
|
|
124
|
+
const preset = PRESETS[presetIndex]?.id ?? "custom";
|
|
125
|
+
const [brokers, setBrokers] = useState(state.kafkaBrokers);
|
|
126
|
+
const [topicPrefix, setTopicPrefix] = useState(state.kafkaTopicPrefix || "com.rulebricks.");
|
|
127
|
+
const [awsRegion, setAwsRegion] = useState(state.kafkaSaslRegion || state.region);
|
|
128
|
+
const [awsRole, setAwsRole] = useState(state.kafkaIdentityAwsRoleArn);
|
|
129
|
+
const [provisionTopics, setProvisionTopics] = useState(state.kafkaProvisionTopics);
|
|
130
|
+
const [azureConnection, setAzureConnection] = useState(state.kafkaSaslPassword);
|
|
131
|
+
const [gcpUsername, setGcpUsername] = useState(state.kafkaSaslUsername);
|
|
132
|
+
const [gcpPassword, setGcpPassword] = useState(state.kafkaSaslPassword);
|
|
133
|
+
const [mechIndex, setMechIndex] = useState(Math.max(0, CUSTOM_MECH.findIndex((m) => m.id === state.kafkaSaslMechanism)));
|
|
134
|
+
const customMechanism = CUSTOM_MECH[mechIndex]?.id ?? "";
|
|
135
|
+
const [customSsl, setCustomSsl] = useState(state.kafkaSsl);
|
|
136
|
+
const [customUsername, setCustomUsername] = useState(state.kafkaSaslUsername);
|
|
137
|
+
const [customPassword, setCustomPassword] = useState(state.kafkaSaslPassword);
|
|
138
|
+
// Postgres
|
|
139
|
+
const [pgModeIndex, setPgModeIndex] = useState(0);
|
|
140
|
+
const [pgConn, setPgConn] = useState("");
|
|
141
|
+
const [pgHost, setPgHost] = useState(state.postgresHost);
|
|
142
|
+
const [pgPort, setPgPort] = useState(String(state.postgresPort || 5432));
|
|
143
|
+
const [pgDatabase, setPgDatabase] = useState(state.postgresDatabase || "postgres");
|
|
144
|
+
const [pgMasterUser, setPgMasterUser] = useState(state.postgresMasterUsername || "postgres");
|
|
145
|
+
const [pgMasterPass, setPgMasterPass] = useState(state.postgresMasterPassword);
|
|
146
|
+
// ----- chaining across the chosen services -----
|
|
147
|
+
const chosen = () => SERVICE_ORDER.filter((k) => selected[k]);
|
|
148
|
+
const firstFieldOf = (k) => k === "redis"
|
|
149
|
+
? "redis-host"
|
|
150
|
+
: k === "kafka"
|
|
151
|
+
? "kafka-preset"
|
|
152
|
+
: "pg-input-mode";
|
|
153
|
+
const goToFirstService = () => {
|
|
154
|
+
const order = chosen();
|
|
155
|
+
if (order.length === 0) {
|
|
156
|
+
persist({});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
setField(firstFieldOf(order[0]));
|
|
160
|
+
};
|
|
161
|
+
const isLastChosen = (k) => {
|
|
162
|
+
const order = chosen();
|
|
163
|
+
return order[order.length - 1] === k;
|
|
164
|
+
};
|
|
165
|
+
const goAfter = (k, overrides = {}) => {
|
|
166
|
+
const order = chosen();
|
|
167
|
+
const next = order[order.indexOf(k) + 1];
|
|
168
|
+
if (next)
|
|
169
|
+
setField(firstFieldOf(next));
|
|
170
|
+
else
|
|
171
|
+
persist(overrides);
|
|
172
|
+
};
|
|
173
|
+
const persist = (overrides) => {
|
|
174
|
+
const redisExternal = selected.redis;
|
|
175
|
+
const kafkaExternal = selected.kafka;
|
|
176
|
+
const postgresExternal = selected.postgres;
|
|
177
|
+
const redisMode = redisExternal ? "external" : "embedded";
|
|
178
|
+
const kafkaMode = kafkaExternal ? "external" : "embedded";
|
|
179
|
+
const postgresMode = postgresExternal ? "external" : "embedded";
|
|
180
|
+
// Derive Kafka SASL/SSL/identity from the chosen preset.
|
|
181
|
+
let kafkaSsl = false;
|
|
182
|
+
let mechanism = "";
|
|
183
|
+
let region = "";
|
|
184
|
+
let username = "";
|
|
185
|
+
let password = "";
|
|
186
|
+
let awsRoleArn = "";
|
|
187
|
+
if (kafkaMode === "external") {
|
|
188
|
+
if (preset === "aws-msk-iam") {
|
|
189
|
+
kafkaSsl = true;
|
|
190
|
+
mechanism = "aws-iam";
|
|
191
|
+
region = awsRegion.trim();
|
|
192
|
+
awsRoleArn = awsRole.trim();
|
|
193
|
+
}
|
|
194
|
+
else if (preset === "azure-event-hubs") {
|
|
195
|
+
kafkaSsl = true;
|
|
196
|
+
mechanism = "plain";
|
|
197
|
+
username = "$ConnectionString";
|
|
198
|
+
password = azureConnection;
|
|
199
|
+
}
|
|
200
|
+
else if (preset === "gcp-managed") {
|
|
201
|
+
kafkaSsl = true;
|
|
202
|
+
mechanism = "plain";
|
|
203
|
+
username = gcpUsername;
|
|
204
|
+
password = gcpPassword;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
kafkaSsl = overrides.customSsl ?? customSsl;
|
|
208
|
+
mechanism = customMechanism;
|
|
209
|
+
username = customMechanism ? customUsername : "";
|
|
210
|
+
password = customMechanism ? customPassword : "";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
dispatch({
|
|
214
|
+
type: "SET_EXTERNAL_SERVICES",
|
|
215
|
+
config: {
|
|
216
|
+
redisMode,
|
|
217
|
+
redisHost: redisExternal ? redisHost.trim() : "",
|
|
218
|
+
redisPort: Number.parseInt(redisPort, 10) || 6379,
|
|
219
|
+
redisPassword: redisExternal ? redisPassword : "",
|
|
220
|
+
redisExistingSecret: redisExternal ? redisExistingSecret.trim() : "",
|
|
221
|
+
redisTls: redisExternal ? redisTls : false,
|
|
222
|
+
kafkaMode,
|
|
223
|
+
kafkaPreset: kafkaMode === "external" ? preset : null,
|
|
224
|
+
kafkaBrokers: kafkaMode === "external" ? brokers.trim() : "",
|
|
225
|
+
kafkaTopicPrefix: kafkaMode === "external" ? topicPrefix.trim() : "com.rulebricks.",
|
|
226
|
+
kafkaSsl,
|
|
227
|
+
kafkaSaslMechanism: mechanism,
|
|
228
|
+
kafkaSaslRegion: region,
|
|
229
|
+
kafkaSaslUsername: username,
|
|
230
|
+
kafkaSaslPassword: password,
|
|
231
|
+
kafkaIdentityAwsRoleArn: awsRoleArn,
|
|
232
|
+
kafkaIdentityGcpServiceAccountEmail: "",
|
|
233
|
+
kafkaProvisionTopics: kafkaMode === "external"
|
|
234
|
+
? (overrides.provisionTopics ?? provisionTopics)
|
|
235
|
+
: true,
|
|
236
|
+
postgresMode,
|
|
237
|
+
postgresHost: postgresExternal ? pgHost.trim() : "",
|
|
238
|
+
postgresPort: Number.parseInt(pgPort, 10) || 5432,
|
|
239
|
+
postgresDatabase: postgresExternal
|
|
240
|
+
? pgDatabase.trim() || "postgres"
|
|
241
|
+
: "postgres",
|
|
242
|
+
postgresMasterUsername: postgresExternal
|
|
243
|
+
? pgMasterUser.trim() || "postgres"
|
|
244
|
+
: "postgres",
|
|
245
|
+
postgresMasterPassword: postgresExternal ? pgMasterPass : "",
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
onComplete();
|
|
249
|
+
};
|
|
250
|
+
const firstKafkaAuthField = () => {
|
|
251
|
+
if (preset === "aws-msk-iam")
|
|
252
|
+
return "kafka-aws-region";
|
|
253
|
+
if (preset === "azure-event-hubs")
|
|
254
|
+
return "kafka-azure-connection";
|
|
255
|
+
if (preset === "gcp-managed")
|
|
256
|
+
return "kafka-gcp-username";
|
|
257
|
+
return "kafka-custom-mechanism";
|
|
258
|
+
};
|
|
259
|
+
// Forward navigation for text fields (selects advance in their onSelect).
|
|
260
|
+
const advance = (from) => {
|
|
261
|
+
setError(null);
|
|
262
|
+
switch (from) {
|
|
263
|
+
case "redis-host":
|
|
264
|
+
if (!redisHost.trim()) {
|
|
265
|
+
setError("Redis host is required for an external instance.");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
setField("redis-port");
|
|
269
|
+
return;
|
|
270
|
+
case "redis-port":
|
|
271
|
+
setField("redis-tls");
|
|
272
|
+
return;
|
|
273
|
+
case "redis-password":
|
|
274
|
+
if (redisPassword)
|
|
275
|
+
goAfter("redis");
|
|
276
|
+
else
|
|
277
|
+
setField("redis-existing-secret");
|
|
278
|
+
return;
|
|
279
|
+
case "redis-existing-secret":
|
|
280
|
+
goAfter("redis");
|
|
281
|
+
return;
|
|
282
|
+
case "kafka-brokers":
|
|
283
|
+
if (!brokers.trim()) {
|
|
284
|
+
setError("At least one broker is required for external Kafka.");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
setField("kafka-topic-prefix");
|
|
288
|
+
return;
|
|
289
|
+
case "kafka-topic-prefix":
|
|
290
|
+
setField(firstKafkaAuthField());
|
|
291
|
+
return;
|
|
292
|
+
case "kafka-aws-region":
|
|
293
|
+
if (!awsRegion.trim()) {
|
|
294
|
+
setError("Region is required for MSK IAM signing.");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
setField("kafka-aws-role");
|
|
298
|
+
return;
|
|
299
|
+
case "kafka-aws-role":
|
|
300
|
+
setField("kafka-provision-topics");
|
|
301
|
+
return;
|
|
302
|
+
case "kafka-azure-connection":
|
|
303
|
+
if (!azureConnection.trim()) {
|
|
304
|
+
setError("Event Hubs connection string is required.");
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
goAfter("kafka");
|
|
308
|
+
return;
|
|
309
|
+
case "kafka-gcp-username":
|
|
310
|
+
setField("kafka-gcp-password");
|
|
311
|
+
return;
|
|
312
|
+
case "kafka-gcp-password":
|
|
313
|
+
goAfter("kafka");
|
|
314
|
+
return;
|
|
315
|
+
case "kafka-custom-username":
|
|
316
|
+
setField("kafka-custom-password");
|
|
317
|
+
return;
|
|
318
|
+
case "kafka-custom-password":
|
|
319
|
+
goAfter("kafka");
|
|
320
|
+
return;
|
|
321
|
+
case "pg-conn": {
|
|
322
|
+
const parsed = parsePostgresUrl(pgConn);
|
|
323
|
+
if (!parsed || !parsed.host) {
|
|
324
|
+
setError("Enter a valid connection string, e.g. postgresql://user:pass@host:5432/postgres");
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
setPgHost(parsed.host);
|
|
328
|
+
if (parsed.port)
|
|
329
|
+
setPgPort(String(parsed.port));
|
|
330
|
+
if (parsed.database)
|
|
331
|
+
setPgDatabase(parsed.database);
|
|
332
|
+
if (parsed.user)
|
|
333
|
+
setPgMasterUser(parsed.user);
|
|
334
|
+
if (parsed.password)
|
|
335
|
+
setPgMasterPass(parsed.password);
|
|
336
|
+
// Confirm/edit the parsed values via the structured fields.
|
|
337
|
+
setField("pg-host");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
case "pg-host":
|
|
341
|
+
if (!pgHost.trim()) {
|
|
342
|
+
setError("Database host/endpoint is required.");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
setField("pg-port");
|
|
346
|
+
return;
|
|
347
|
+
case "pg-port":
|
|
348
|
+
setField("pg-database");
|
|
349
|
+
return;
|
|
350
|
+
case "pg-database":
|
|
351
|
+
setField("pg-master-username");
|
|
352
|
+
return;
|
|
353
|
+
case "pg-master-username":
|
|
354
|
+
setField("pg-master-password");
|
|
355
|
+
return;
|
|
356
|
+
case "pg-master-password":
|
|
357
|
+
if (!pgMasterPass) {
|
|
358
|
+
setError("Master password is required to initialize the database (roles, schemas).");
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
goAfter("postgres");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
const prevServiceField = (k) => {
|
|
366
|
+
const order = chosen();
|
|
367
|
+
const prev = order[order.indexOf(k) - 1];
|
|
368
|
+
if (!prev)
|
|
369
|
+
return "which";
|
|
370
|
+
// Land on the last field of the previous service.
|
|
371
|
+
if (prev === "redis")
|
|
372
|
+
return "redis-password";
|
|
373
|
+
if (prev === "kafka")
|
|
374
|
+
return "kafka-preset";
|
|
375
|
+
return "pg-master-password";
|
|
376
|
+
};
|
|
377
|
+
const handleBack = () => {
|
|
378
|
+
setError(null);
|
|
379
|
+
switch (field) {
|
|
380
|
+
case "mode":
|
|
381
|
+
onBack();
|
|
382
|
+
return;
|
|
383
|
+
case "which":
|
|
384
|
+
setField("mode");
|
|
385
|
+
return;
|
|
386
|
+
case "redis-host":
|
|
387
|
+
setField(prevServiceField("redis"));
|
|
388
|
+
return;
|
|
389
|
+
case "redis-port":
|
|
390
|
+
setField("redis-host");
|
|
391
|
+
return;
|
|
392
|
+
case "redis-tls":
|
|
393
|
+
setField("redis-port");
|
|
394
|
+
return;
|
|
395
|
+
case "redis-password":
|
|
396
|
+
setField("redis-tls");
|
|
397
|
+
return;
|
|
398
|
+
case "redis-existing-secret":
|
|
399
|
+
setField("redis-password");
|
|
400
|
+
return;
|
|
401
|
+
case "kafka-preset":
|
|
402
|
+
setField(prevServiceField("kafka"));
|
|
403
|
+
return;
|
|
404
|
+
case "kafka-brokers":
|
|
405
|
+
setField("kafka-preset");
|
|
406
|
+
return;
|
|
407
|
+
case "kafka-topic-prefix":
|
|
408
|
+
setField("kafka-brokers");
|
|
409
|
+
return;
|
|
410
|
+
case "kafka-aws-region":
|
|
411
|
+
case "kafka-azure-connection":
|
|
412
|
+
case "kafka-gcp-username":
|
|
413
|
+
case "kafka-custom-mechanism":
|
|
414
|
+
setField("kafka-topic-prefix");
|
|
415
|
+
return;
|
|
416
|
+
case "kafka-aws-role":
|
|
417
|
+
setField("kafka-aws-region");
|
|
418
|
+
return;
|
|
419
|
+
case "kafka-provision-topics":
|
|
420
|
+
setField("kafka-aws-role");
|
|
421
|
+
return;
|
|
422
|
+
case "kafka-gcp-password":
|
|
423
|
+
setField("kafka-gcp-username");
|
|
424
|
+
return;
|
|
425
|
+
case "kafka-custom-ssl":
|
|
426
|
+
setField("kafka-custom-mechanism");
|
|
427
|
+
return;
|
|
428
|
+
case "kafka-custom-username":
|
|
429
|
+
setField("kafka-custom-ssl");
|
|
430
|
+
return;
|
|
431
|
+
case "kafka-custom-password":
|
|
432
|
+
setField("kafka-custom-username");
|
|
433
|
+
return;
|
|
434
|
+
case "pg-input-mode":
|
|
435
|
+
setField(prevServiceField("postgres"));
|
|
436
|
+
return;
|
|
437
|
+
case "pg-conn":
|
|
438
|
+
setField("pg-input-mode");
|
|
439
|
+
return;
|
|
440
|
+
case "pg-host":
|
|
441
|
+
setField("pg-input-mode");
|
|
442
|
+
return;
|
|
443
|
+
case "pg-port":
|
|
444
|
+
setField("pg-host");
|
|
445
|
+
return;
|
|
446
|
+
case "pg-database":
|
|
447
|
+
setField("pg-port");
|
|
448
|
+
return;
|
|
449
|
+
case "pg-master-username":
|
|
450
|
+
setField("pg-database");
|
|
451
|
+
return;
|
|
452
|
+
case "pg-master-password":
|
|
453
|
+
setField("pg-master-username");
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
useInput((input, key) => {
|
|
458
|
+
if (field === "which") {
|
|
459
|
+
if (key.escape) {
|
|
460
|
+
setField("mode");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (key.upArrow) {
|
|
464
|
+
setWhichIndex((i) => Math.max(0, i - 1));
|
|
465
|
+
}
|
|
466
|
+
else if (key.downArrow) {
|
|
467
|
+
setWhichIndex((i) => Math.min(whichItems.length, i + 1));
|
|
468
|
+
}
|
|
469
|
+
else if (input === " " || input === "x") {
|
|
470
|
+
if (whichIndex < whichItems.length) {
|
|
471
|
+
const k = whichItems[whichIndex].key;
|
|
472
|
+
setSelected((s) => ({ ...s, [k]: !s[k] }));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else if (key.return) {
|
|
476
|
+
if (whichIndex === whichItems.length) {
|
|
477
|
+
if (!selected.redis && !selected.kafka && !selected.postgres) {
|
|
478
|
+
setError("Select at least one service to externalize.");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
setError(null);
|
|
482
|
+
goToFirstService();
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
const k = whichItems[whichIndex].key;
|
|
486
|
+
setSelected((s) => ({ ...s, [k]: !s[k] }));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (key.escape) {
|
|
492
|
+
handleBack();
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
// ===== Select handlers =====
|
|
496
|
+
const handleModeSelect = (item) => {
|
|
497
|
+
if (item.value === "dedicated") {
|
|
498
|
+
setSelected({ redis: false, kafka: false, postgres: false });
|
|
499
|
+
dispatch({
|
|
500
|
+
type: "SET_EXTERNAL_SERVICES",
|
|
501
|
+
config: {
|
|
502
|
+
redisMode: "embedded",
|
|
503
|
+
kafkaMode: "embedded",
|
|
504
|
+
postgresMode: "embedded",
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
onComplete();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
setField("which");
|
|
511
|
+
};
|
|
512
|
+
const handleRedisTlsSelect = (item) => {
|
|
513
|
+
setRedisTls(item.value === "yes");
|
|
514
|
+
setField("redis-password");
|
|
515
|
+
};
|
|
516
|
+
const handlePresetSelect = (item) => {
|
|
517
|
+
setPresetIndex(Math.max(0, PRESETS.findIndex((p) => p.id === item.value)));
|
|
518
|
+
setField("kafka-brokers");
|
|
519
|
+
};
|
|
520
|
+
const handleMechanismSelect = (item) => {
|
|
521
|
+
setMechIndex(Math.max(0, CUSTOM_MECH.findIndex((m) => m.id === item.value)));
|
|
522
|
+
setField("kafka-custom-ssl");
|
|
523
|
+
};
|
|
524
|
+
const handleCustomSslSelect = (item) => {
|
|
525
|
+
const ssl = item.value === "yes";
|
|
526
|
+
setCustomSsl(ssl);
|
|
527
|
+
// A SASL mechanism needs credentials next; SSL-only ends Kafka here. Pass the
|
|
528
|
+
// freshly chosen ssl value to avoid reading stale state when persisting.
|
|
529
|
+
if (customMechanism) {
|
|
530
|
+
setField("kafka-custom-username");
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
goAfter("kafka", { customSsl: ssl });
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
const handleProvisionTopicsSelect = (item) => {
|
|
537
|
+
const provision = item.value === "yes";
|
|
538
|
+
setProvisionTopics(provision);
|
|
539
|
+
// Pass the fresh value so persist doesn't read stale state (as with customSsl).
|
|
540
|
+
goAfter("kafka", { provisionTopics: provision });
|
|
541
|
+
};
|
|
542
|
+
const handlePgModeSelect = (item) => {
|
|
543
|
+
setPgModeIndex(Math.max(0, PG_INPUT_MODES.findIndex((m) => m.value === item.value)));
|
|
544
|
+
setField(item.value === "connstring" ? "pg-conn" : "pg-host");
|
|
545
|
+
};
|
|
546
|
+
// ===== Renderers =====
|
|
547
|
+
const renderSelect = (label, items, onSelect, initialIndex = 0, note) => (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: label }), note && (_jsx(Text, { color: "gray", dimColor: true, children: note })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(SelectInput, { items: items, onSelect: onSelect, initialIndex: initialIndex, indicatorComponent: () => null, itemComponent: ({ isSelected, label: itemLabel }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", itemLabel] })) }) }), _jsx(Text, { color: colors.muted, children: "\u2191/\u2193 to choose \u2022 Enter to continue" })] }));
|
|
548
|
+
const renderText = (label, value, onChange, opts = {}) => (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: label }), opts.hint && (_jsx(Text, { color: "gray", dimColor: true, children: opts.hint })), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: value, onChange: onChange, onSubmit: () => advance(field), placeholder: opts.placeholder, mask: opts.mask ? "*" : undefined }) })] }));
|
|
549
|
+
return (_jsxs(BorderBox, { title: "External Services", children: [_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: "Managed services for Rulebricks." }), _jsxs(Text, { color: "gray", dimColor: true, children: ["By default Redis and Kafka run in-cluster, managed by the chart. You can instead connect to managed providers you already operate", pgAvailable ? ", including your Postgres database." : "."] })] }), field === "mode" &&
|
|
550
|
+
renderSelect("How should these services be provided?", MODE_OPTIONS, handleModeSelect, state.redisMode === "external" ||
|
|
551
|
+
state.kafkaMode === "external" ||
|
|
552
|
+
state.postgresMode === "external"
|
|
553
|
+
? 1
|
|
554
|
+
: 0), field === "which" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Which services do you want to connect to managed providers?" }), _jsx(Text, { color: "gray", dimColor: true, children: "Space/Enter to toggle \u2022 \u2191/\u2193 to navigate" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [whichItems.map((item, index) => {
|
|
555
|
+
const isCursor = index === whichIndex;
|
|
556
|
+
const isOn = selected[item.key];
|
|
557
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: isCursor ? colors.accent : undefined, children: isCursor ? "❯ " : " " }), _jsx(Text, { color: isOn ? colors.success : colors.muted, children: isOn ? "[✓]" : "[ ]" }), _jsxs(Text, { color: isCursor ? colors.accent : undefined, children: [" ", item.label] })] }), isCursor && (_jsx(Box, { marginLeft: 6, children: _jsx(Text, { color: "gray", dimColor: true, children: item.hint }) }))] }, item.key));
|
|
558
|
+
}), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: whichIndex === whichItems.length ? colors.accent : colors.muted, children: whichIndex === whichItems.length ? "❯ " : " " }), _jsx(Text, { color: whichIndex === whichItems.length
|
|
559
|
+
? colors.success
|
|
560
|
+
: colors.muted, bold: whichIndex === whichItems.length, children: "[Continue \u2192]" })] })] }), !pgAvailable && (_jsx(Text, { color: "gray", dimColor: true, children: "Externalizing the Postgres database is available on AWS and Azure." }))] })), field === "redis-host" &&
|
|
561
|
+
renderText("Redis host", redisHost, setRedisHost, {
|
|
562
|
+
hint: "Hostname of your managed Redis (e.g. ElastiCache/Memorystore endpoint).",
|
|
563
|
+
placeholder: "redis.example.com",
|
|
564
|
+
}), field === "redis-port" &&
|
|
565
|
+
renderText("Redis port", redisPort, setRedisPort, {
|
|
566
|
+
placeholder: "6379",
|
|
567
|
+
}), field === "redis-tls" &&
|
|
568
|
+
renderSelect("Redis TLS", yesNo("Yes - connect using rediss:// (TLS)", "No - plaintext redis://"), handleRedisTlsSelect, redisTls ? 1 : 0), field === "redis-password" &&
|
|
569
|
+
renderText("Redis password", redisPassword, setRedisPassword, {
|
|
570
|
+
hint: "Leave blank to use an existing secret or no auth.",
|
|
571
|
+
mask: true,
|
|
572
|
+
}), field === "redis-existing-secret" &&
|
|
573
|
+
renderText("Redis password secret", redisExistingSecret, setRedisExistingSecret, {
|
|
574
|
+
hint: "Name of an existing Kubernetes secret holding the password. Blank = no auth.",
|
|
575
|
+
placeholder: "my-redis-auth",
|
|
576
|
+
}), field === "kafka-preset" &&
|
|
577
|
+
renderSelect("Managed Kafka type", PRESETS.map((p) => ({ label: p.label, value: p.id })), handlePresetSelect, presetIndex, "* Topics/partitions may need to be created by a Kafka admin to match worker counts."), field === "kafka-brokers" &&
|
|
578
|
+
renderText("Kafka bootstrap brokers", brokers, setBrokers, {
|
|
579
|
+
hint: "Comma-separated host:port list.",
|
|
580
|
+
placeholder: "b-1.example:9098,b-2.example:9098",
|
|
581
|
+
}), field === "kafka-topic-prefix" &&
|
|
582
|
+
renderText("Topic prefix", topicPrefix, setTopicPrefix, {
|
|
583
|
+
hint: "Namespaces topic names (e.g. com.rulebricks.solution) to avoid collisions on shared Kafka. Blank = no prefix.",
|
|
584
|
+
placeholder: "com.rulebricks.",
|
|
585
|
+
}), field === "kafka-aws-region" &&
|
|
586
|
+
renderText("AWS region", awsRegion, setAwsRegion, {
|
|
587
|
+
hint: "Region of the MSK cluster (used to sign IAM auth tokens).",
|
|
588
|
+
placeholder: "us-east-1",
|
|
589
|
+
}), field === "kafka-aws-role" &&
|
|
590
|
+
renderText("MSK IAM role ARN", awsRole, setAwsRole, {
|
|
591
|
+
hint: "Pod Identity role for HPS + the Vector bridge (the cluster-setup RulebricksRole). Blank to reuse the SAs' association.",
|
|
592
|
+
placeholder: "arn:aws:iam::123456789012:role/rulebricks-cluster-rulebricks",
|
|
593
|
+
}), field === "kafka-provision-topics" &&
|
|
594
|
+
renderSelect("Kafka topic provisioning", yesNo("Yes - the chart creates the required topics on the broker", "No - I manage topics myself (locked-down / no CreateTopic)"), handleProvisionTopicsSelect, provisionTopics ? 1 : 0), field === "kafka-azure-connection" &&
|
|
595
|
+
renderText("Event Hubs connection string", azureConnection, setAzureConnection, {
|
|
596
|
+
hint: "Namespace connection string (used as the SASL PLAIN password).",
|
|
597
|
+
placeholder: "Endpoint=sb://...;SharedAccessKey=...",
|
|
598
|
+
mask: true,
|
|
599
|
+
}), field === "kafka-gcp-username" &&
|
|
600
|
+
renderText("Kafka username", gcpUsername, setGcpUsername, {
|
|
601
|
+
hint: "GCP service account principal for SASL PLAIN.",
|
|
602
|
+
placeholder: "service-account@project.iam.gserviceaccount.com",
|
|
603
|
+
}), field === "kafka-gcp-password" &&
|
|
604
|
+
renderText("Kafka password", gcpPassword, setGcpPassword, {
|
|
605
|
+
hint: "Service-account key or access token.",
|
|
606
|
+
mask: true,
|
|
607
|
+
}), field === "kafka-custom-mechanism" &&
|
|
608
|
+
renderSelect("SASL mechanism", CUSTOM_MECH.map((m) => ({ label: m.label, value: m.id })), handleMechanismSelect, mechIndex), field === "kafka-custom-ssl" &&
|
|
609
|
+
renderSelect("Kafka TLS/SSL", yesNo("Yes - connect over TLS", "No - plaintext connection"), handleCustomSslSelect, customSsl ? 1 : 0), field === "kafka-custom-username" &&
|
|
610
|
+
renderText("Kafka SASL username", customUsername, setCustomUsername, {}), field === "kafka-custom-password" &&
|
|
611
|
+
renderText("Kafka SASL password", customPassword, setCustomPassword, {
|
|
612
|
+
mask: true,
|
|
613
|
+
}), field === "pg-input-mode" &&
|
|
614
|
+
renderSelect(state.provider === "aws"
|
|
615
|
+
? "How do you want to provide your RDS / Aurora connection?"
|
|
616
|
+
: "How do you want to provide your Flexible Server connection?", PG_INPUT_MODES, handlePgModeSelect, pgModeIndex, "Self-hosted Supabase will run against this database. A one-time bootstrap initializes roles/schemas; provide the master/admin credentials."), field === "pg-conn" &&
|
|
617
|
+
renderText("Postgres connection string", pgConn, setPgConn, {
|
|
618
|
+
hint: "Parsed into the fields below (you can review them next). Use the admin/master user.",
|
|
619
|
+
placeholder: "postgresql://postgres:pass@host:5432/postgres",
|
|
620
|
+
mask: true,
|
|
621
|
+
}), field === "pg-host" &&
|
|
622
|
+
renderText(state.provider === "aws" ? "RDS endpoint" : "Server host", pgHost, setPgHost, {
|
|
623
|
+
hint: state.provider === "aws"
|
|
624
|
+
? "Writer/instance endpoint. Use the direct endpoint (not a proxy/pooler)."
|
|
625
|
+
: "Fully-qualified server name.",
|
|
626
|
+
placeholder: state.provider === "aws"
|
|
627
|
+
? "db.cluster-xxxx.us-east-1.rds.amazonaws.com"
|
|
628
|
+
: "myserver.postgres.database.azure.com",
|
|
629
|
+
}), field === "pg-port" &&
|
|
630
|
+
renderText("Database port", pgPort, setPgPort, { placeholder: "5432" }), field === "pg-database" &&
|
|
631
|
+
renderText("Database name", pgDatabase, setPgDatabase, {
|
|
632
|
+
hint: "The database Supabase services connect to.",
|
|
633
|
+
placeholder: "postgres",
|
|
634
|
+
}), field === "pg-master-username" &&
|
|
635
|
+
renderText("Master/admin username", pgMasterUser, setPgMasterUser, {
|
|
636
|
+
hint: state.provider === "aws"
|
|
637
|
+
? "RDS master username (recommended: postgres). Used once to create roles/schemas."
|
|
638
|
+
: "Azure server admin username. Used once to create roles/schemas.",
|
|
639
|
+
placeholder: "postgres",
|
|
640
|
+
}), field === "pg-master-password" &&
|
|
641
|
+
renderText("Master/admin password", pgMasterPass, setPgMasterPass, {
|
|
642
|
+
hint: "Used by the one-time bootstrap to initialize the database. Stored in a short-lived secret.",
|
|
643
|
+
mask: true,
|
|
644
|
+
}), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.error, children: ["\u2717 ", error] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back" }) })] }));
|
|
645
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
interface FeatureConfigStepProps {
|
|
2
2
|
onComplete: () => void;
|
|
3
3
|
onBack: () => void;
|
|
4
|
+
entryDirection?: "forward" | "back";
|
|
4
5
|
}
|
|
5
|
-
export declare function FeatureConfigStep({ onComplete, onBack, }: FeatureConfigStepProps): import("react/jsx-runtime").JSX.Element | null;
|
|
6
|
+
export declare function FeatureConfigStep({ onComplete, onBack, entryDirection, }: FeatureConfigStepProps): import("react/jsx-runtime").JSX.Element | null;
|
|
6
7
|
export {};
|