@stacksjs/ts-cloud 0.7.93 → 0.7.95
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/dist/bin/cli.js +1188 -1184
- package/dist/bin/dashboard-server.js +413 -409
- package/dist/{chunk-y5wrqyyz.js → chunk-3d4204h1.js} +365 -1
- package/dist/{chunk-fbwcv2vf.js → chunk-xp3txtg9.js} +458 -42
- package/dist/deploy/dashboard-database.d.ts +91 -3
- package/dist/deploy/dashboard-vitess.d.ts +154 -0
- package/dist/deploy/dashboard-vitess.test.d.ts +1 -0
- package/dist/deploy/index.js +2 -2
- package/dist/drivers/hetzner/client.d.ts +96 -0
- package/dist/drivers/hetzner/migrate-server.d.ts +124 -0
- package/dist/drivers/hetzner/migrate-server.test.d.ts +1 -0
- package/dist/drivers/hetzner/role-swap.d.ts +139 -0
- package/dist/drivers/hetzner/role-swap.test.d.ts +1 -0
- package/dist/drivers/index.js +1 -1
- package/dist/drivers/shared/db-provision.d.ts +4 -0
- package/dist/drivers/shared/deploy-script.d.ts +17 -1
- package/dist/drivers/shared/package-manager.d.ts +2 -0
- package/dist/drivers/shared/releases.d.ts +41 -2
- package/dist/drivers/shared/vitess-provision.d.ts +133 -0
- package/dist/drivers/shared/vitess-provision.test.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -2
- package/dist/ui/access-denied.html +2 -2
- package/dist/ui/account/automation.html +3 -3
- package/dist/ui/applications/compose.html +4 -4
- package/dist/ui/applications/new.html +2 -2
- package/dist/ui/data/backups.html +4 -4
- package/dist/ui/data/services.html +4 -4
- package/dist/ui/data/volumes.html +4 -4
- package/dist/ui/index.html +4 -4
- package/dist/ui/integrations.html +2 -2
- package/dist/ui/operations/alerts.html +4 -4
- package/dist/ui/operations/configuration.html +4 -4
- package/dist/ui/operations/jobs.html +4 -4
- package/dist/ui/operations/maintenance.html +4 -4
- package/dist/ui/operations/observability.html +4 -4
- package/dist/ui/operations/previews.html +4 -4
- package/dist/ui/operations/queue.html +4 -4
- package/dist/ui/operations/regions.html +4 -4
- package/dist/ui/operations/releases.html +4 -4
- package/dist/ui/operations/workloads.html +4 -4
- package/dist/ui/security.html +2 -2
- package/dist/ui/server/actions.html +4 -4
- package/dist/ui/server/activity.html +2 -2
- package/dist/ui/server/capacity.html +3 -3
- package/dist/ui/server/database.html +123 -7
- package/dist/ui/server/deployments.html +4 -4
- package/dist/ui/server/firewall.html +4 -4
- package/dist/ui/server/fleet.html +4 -4
- package/dist/ui/server/logs.html +4 -4
- package/dist/ui/server/metrics.html +4 -4
- package/dist/ui/server/security.html +2 -2
- package/dist/ui/server/services.html +2 -2
- package/dist/ui/server/sites.html +4 -4
- package/dist/ui/server/ssh-keys.html +4 -4
- package/dist/ui/server/team.html +4 -4
- package/dist/ui/server/terminal.html +2 -2
- package/dist/ui/serverless/alarms.html +4 -4
- package/dist/ui/serverless/assets.html +2 -2
- package/dist/ui/serverless/cost.html +2 -2
- package/dist/ui/serverless/data.html +4 -4
- package/dist/ui/serverless/firewall.html +2 -2
- package/dist/ui/serverless/functions.html +4 -4
- package/dist/ui/serverless/logs.html +4 -4
- package/dist/ui/serverless/metrics.html +2 -2
- package/dist/ui/serverless/queues.html +4 -4
- package/dist/ui/serverless/secrets.html +4 -4
- package/dist/ui/serverless/traces.html +4 -4
- package/dist/ui/serverless.html +4 -4
- package/dist/ui-src/pages/server/database.stx +257 -9
- package/package.json +3 -3
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
resolveSiteKind,
|
|
46
46
|
resolveUiSource,
|
|
47
47
|
siteInstallBase
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-xp3txtg9.js";
|
|
49
49
|
import {
|
|
50
50
|
artifactKey,
|
|
51
51
|
buildCloudFormationTemplate,
|
|
@@ -39487,6 +39487,10 @@ async function resolveServerDashboardData(config6, environment2, options = {}) {
|
|
|
39487
39487
|
}
|
|
39488
39488
|
|
|
39489
39489
|
// src/deploy/dashboard-database.ts
|
|
39490
|
+
var EXTERNAL_ENGINES = new Set(["singlestore", "vitess"]);
|
|
39491
|
+
function isExternalEngine(engine) {
|
|
39492
|
+
return EXTERNAL_ENGINES.has(engine);
|
|
39493
|
+
}
|
|
39490
39494
|
var SOCKETS = {
|
|
39491
39495
|
mysql: "/var/lib/pantry/mysql/mysqld.sock",
|
|
39492
39496
|
mariadb: "/var/lib/pantry/mariadb/mariadbd.sock"
|
|
@@ -39499,6 +39503,10 @@ function normalizeEngine(engine) {
|
|
|
39499
39503
|
return "postgres";
|
|
39500
39504
|
if (engine === "mariadb")
|
|
39501
39505
|
return "mariadb";
|
|
39506
|
+
if (engine === "singlestore")
|
|
39507
|
+
return "singlestore";
|
|
39508
|
+
if (engine === "vitess")
|
|
39509
|
+
return "vitess";
|
|
39502
39510
|
return "mysql";
|
|
39503
39511
|
}
|
|
39504
39512
|
function resolveDbEngine(config6) {
|
|
@@ -39525,6 +39533,8 @@ function pgExec(sql, database) {
|
|
|
39525
39533
|
return [`${pgAdminCommand(database)} -tA <<'TS_CLOUD_PG_EOF'`, ...sql, "TS_CLOUD_PG_EOF"];
|
|
39526
39534
|
}
|
|
39527
39535
|
function buildListScript(engine, database) {
|
|
39536
|
+
if (engine === "vitess")
|
|
39537
|
+
return buildVitessListScript(database);
|
|
39528
39538
|
if (engine === "postgres") {
|
|
39529
39539
|
return pgExec([
|
|
39530
39540
|
"SELECT 'DB=' || datname FROM pg_database WHERE datistemplate = false;",
|
|
@@ -39536,6 +39546,72 @@ function buildListScript(engine, database) {
|
|
|
39536
39546
|
"SELECT DISTINCT CONCAT('USER=', User) FROM mysql.user WHERE User NOT IN ('root', 'mysql.sys', 'mysql.session', 'mysql.infoschema', 'debian-sys-maint');"
|
|
39537
39547
|
]);
|
|
39538
39548
|
}
|
|
39549
|
+
function externalMysqlExec(sql, database, port) {
|
|
39550
|
+
const host2 = database?.host ?? "127.0.0.1";
|
|
39551
|
+
const user = database?.username ?? "root";
|
|
39552
|
+
const pass = database?.password ?? "";
|
|
39553
|
+
const tls = database?.ssl === false ? "" : " --ssl-mode=PREFERRED";
|
|
39554
|
+
const shq = (v) => `'${String(v).replace(/'/g, `'\\''`)}'`;
|
|
39555
|
+
const env = pass ? `MYSQL_PWD=${shq(pass)} ` : "";
|
|
39556
|
+
return [
|
|
39557
|
+
`${env}mysql -h ${shq(host2)} -P ${database?.port ?? port} -u ${shq(user)}${tls} --batch --skip-column-names <<'TS_CLOUD_SQL_EOF'`,
|
|
39558
|
+
...sql,
|
|
39559
|
+
"TS_CLOUD_SQL_EOF"
|
|
39560
|
+
];
|
|
39561
|
+
}
|
|
39562
|
+
var VTGATE_DEFAULT_PORT = 15306;
|
|
39563
|
+
function buildVitessListScript(database) {
|
|
39564
|
+
const script = externalMysqlExec(["SHOW KEYSPACES;"], database, VTGATE_DEFAULT_PORT);
|
|
39565
|
+
script[0] = `${script[0]} | sed 's/^/DB=/'`;
|
|
39566
|
+
return script;
|
|
39567
|
+
}
|
|
39568
|
+
function buildVitessTopologyScript(database) {
|
|
39569
|
+
return externalMysqlExec([
|
|
39570
|
+
`SHOW KEYSPACES;`,
|
|
39571
|
+
`SHOW VITESS_SHARDS;`,
|
|
39572
|
+
`SHOW VITESS_TABLETS;`
|
|
39573
|
+
], database, VTGATE_DEFAULT_PORT);
|
|
39574
|
+
}
|
|
39575
|
+
function parseVitessTopology(output) {
|
|
39576
|
+
const keyspaces = [];
|
|
39577
|
+
const shards = [];
|
|
39578
|
+
const tablets = [];
|
|
39579
|
+
for (const rawLine of output.split(`
|
|
39580
|
+
`)) {
|
|
39581
|
+
const line = rawLine.trim();
|
|
39582
|
+
if (!line)
|
|
39583
|
+
continue;
|
|
39584
|
+
const cols = line.split("\t").map((c) => c.trim()).filter(Boolean);
|
|
39585
|
+
if (cols.length >= 6) {
|
|
39586
|
+
const [cell, keyspace, shard, type, state, alias, hostname] = cols;
|
|
39587
|
+
tablets.push({
|
|
39588
|
+
cell: cell ?? "",
|
|
39589
|
+
keyspace: keyspace ?? "",
|
|
39590
|
+
shard: shard ?? "",
|
|
39591
|
+
type: type ?? "",
|
|
39592
|
+
state: state ?? "",
|
|
39593
|
+
alias: alias ?? "",
|
|
39594
|
+
hostname: hostname ?? ""
|
|
39595
|
+
});
|
|
39596
|
+
continue;
|
|
39597
|
+
}
|
|
39598
|
+
if (cols.length === 1) {
|
|
39599
|
+
const only = cols[0];
|
|
39600
|
+
if (only.includes("/")) {
|
|
39601
|
+
const [keyspace, shard] = only.split("/");
|
|
39602
|
+
if (keyspace && shard)
|
|
39603
|
+
shards.push({ keyspace, shard });
|
|
39604
|
+
} else {
|
|
39605
|
+
keyspaces.push(only);
|
|
39606
|
+
}
|
|
39607
|
+
}
|
|
39608
|
+
}
|
|
39609
|
+
return {
|
|
39610
|
+
keyspaces: [...new Set(keyspaces)].filter(Boolean).sort(),
|
|
39611
|
+
shards,
|
|
39612
|
+
tablets
|
|
39613
|
+
};
|
|
39614
|
+
}
|
|
39539
39615
|
function buildCreateDatabaseScript(engine, name, database) {
|
|
39540
39616
|
if (engine === "postgres") {
|
|
39541
39617
|
return pgExec([
|
|
@@ -39612,18 +39688,31 @@ async function runDb(config6, environment2, commands, comment) {
|
|
|
39612
39688
|
stderr: result2.perInstance?.[0]?.error ?? result2.error ?? ""
|
|
39613
39689
|
};
|
|
39614
39690
|
}
|
|
39691
|
+
function externalEngineRefusal(engine, action) {
|
|
39692
|
+
const where = engine === "vitess" ? "Create a keyspace with `vtctldclient CreateKeyspace`, and manage vtgate credentials on the cluster." : "Use the provider console or the cluster's own admin connection.";
|
|
39693
|
+
return {
|
|
39694
|
+
ok: false,
|
|
39695
|
+
error: `${action} is not available for ${engine}: it is an external cluster, so this box has no engine to administer. ${where}`
|
|
39696
|
+
};
|
|
39697
|
+
}
|
|
39615
39698
|
async function listDatabases(config6, environment2) {
|
|
39616
39699
|
const engine = resolveDbEngine(config6);
|
|
39700
|
+
if (isExternalEngine(engine) && engine !== "vitess")
|
|
39701
|
+
return { ...externalEngineRefusal(engine, "Listing databases"), engine, databases: [], users: [] };
|
|
39617
39702
|
const r = await runDb(config6, environment2, buildListScript(engine, resolveAppDatabase(config6)), "ts-cloud db:list");
|
|
39618
39703
|
const parsed = r.ok && r.stdout ? parseDbList(r.stdout) : { databases: [], users: [] };
|
|
39619
39704
|
return { ...r, engine, ...parsed };
|
|
39620
39705
|
}
|
|
39621
39706
|
async function createDatabase(config6, environment2, name) {
|
|
39622
39707
|
const engine = resolveDbEngine(config6);
|
|
39708
|
+
if (isExternalEngine(engine))
|
|
39709
|
+
return externalEngineRefusal(engine, "Creating a database");
|
|
39623
39710
|
return runDb(config6, environment2, buildCreateDatabaseScript(engine, name, resolveAppDatabase(config6)), `ts-cloud db:create ${name}`);
|
|
39624
39711
|
}
|
|
39625
39712
|
async function createDatabaseUser(config6, environment2, input) {
|
|
39626
39713
|
const engine = resolveDbEngine(config6);
|
|
39714
|
+
if (isExternalEngine(engine))
|
|
39715
|
+
return externalEngineRefusal(engine, "Creating a database user");
|
|
39627
39716
|
return runDb(config6, environment2, buildCreateUserScript(engine, input, resolveAppDatabase(config6)), `ts-cloud db:user ${input.username}`);
|
|
39628
39717
|
}
|
|
39629
39718
|
var DB_BACKUP_DIR = "/var/backups/ts-cloud/databases";
|
|
@@ -39667,6 +39756,8 @@ async function backupDatabase(config6, environment2, name) {
|
|
|
39667
39756
|
if (!isValidDbIdentifier(name))
|
|
39668
39757
|
return { ok: false, error: "Database name must be a valid identifier.", database: name };
|
|
39669
39758
|
const engine = resolveDbEngine(config6);
|
|
39759
|
+
if (isExternalEngine(engine))
|
|
39760
|
+
return { ...externalEngineRefusal(engine, "Backing up"), database: name };
|
|
39670
39761
|
const r = await runDb(config6, environment2, buildBackupScript(engine, name, DB_BACKUP_DIR, resolveAppDatabase(config6)), `ts-cloud db:backup ${name}`);
|
|
39671
39762
|
return { ...r, database: name };
|
|
39672
39763
|
}
|
|
@@ -39675,6 +39766,233 @@ async function listDatabaseBackups(config6, environment2) {
|
|
|
39675
39766
|
const backups = r.ok && r.stdout ? parseBackups(r.stdout) : [];
|
|
39676
39767
|
return { ...r, backups };
|
|
39677
39768
|
}
|
|
39769
|
+
async function describeVitess(config6, environment2) {
|
|
39770
|
+
const engine = resolveDbEngine(config6);
|
|
39771
|
+
const empty = { keyspaces: [], shards: [], tablets: [] };
|
|
39772
|
+
if (engine !== "vitess")
|
|
39773
|
+
return { ok: false, error: "The configured database engine is not Vitess.", engine, ...empty };
|
|
39774
|
+
const r = await runDb(config6, environment2, buildVitessTopologyScript(resolveAppDatabase(config6)), "ts-cloud vitess:topology");
|
|
39775
|
+
const parsed = r.ok && r.stdout ? parseVitessTopology(r.stdout) : empty;
|
|
39776
|
+
return { ...r, engine, ...parsed };
|
|
39777
|
+
}
|
|
39778
|
+
function unhealthyTablets(tablets) {
|
|
39779
|
+
return tablets.filter((t) => t.state.toUpperCase() !== "SERVING");
|
|
39780
|
+
}
|
|
39781
|
+
function shardsMissingPrimary(topology2) {
|
|
39782
|
+
const withPrimary = new Set(topology2.tablets.filter((t) => t.type.toUpperCase() === "PRIMARY" && t.state.toUpperCase() === "SERVING").map((t) => `${t.keyspace}/${t.shard}`));
|
|
39783
|
+
return topology2.shards.filter((s) => !withPrimary.has(`${s.keyspace}/${s.shard}`));
|
|
39784
|
+
}
|
|
39785
|
+
|
|
39786
|
+
// src/deploy/dashboard-vitess.ts
|
|
39787
|
+
var VTCTLDCLIENT_BIN = "/usr/local/bin/vtctldclient";
|
|
39788
|
+
var DEFAULT_VTCTLDCLIENT_VERSION = "21.0.0";
|
|
39789
|
+
function sh(value2) {
|
|
39790
|
+
return `'${String(value2).split("'").join("'\\''")}'`;
|
|
39791
|
+
}
|
|
39792
|
+
function resolveVitessConfig(config6) {
|
|
39793
|
+
return resolveAppDatabase(config6)?.vitess ?? {};
|
|
39794
|
+
}
|
|
39795
|
+
function isValidKeyspaceName(value2) {
|
|
39796
|
+
return /^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(value2);
|
|
39797
|
+
}
|
|
39798
|
+
function isValidMigrationUuid(value2) {
|
|
39799
|
+
return /^[0-9a-f]{8}(?:_[0-9a-f]{4}){3}_[0-9a-f]{12}$/i.test(value2);
|
|
39800
|
+
}
|
|
39801
|
+
function buildVtctldClientInstallScript(version3 = DEFAULT_VTCTLDCLIENT_VERSION) {
|
|
39802
|
+
const v = sh(version3);
|
|
39803
|
+
return [
|
|
39804
|
+
`if ${VTCTLDCLIENT_BIN} --version 2>/dev/null | grep -q ${v}; then echo "vtctldclient ${version3} already installed"; else`,
|
|
39805
|
+
" command -v curl >/dev/null 2>&1 || (apt-get update -y && apt-get install -y curl ca-certificates)",
|
|
39806
|
+
' if [ "$(uname -m)" != "x86_64" ]; then',
|
|
39807
|
+
' echo "vtctldclient: Vitess publishes only an x86_64 release tarball; this box is $(uname -m). Build from source or install via pantry." >&2',
|
|
39808
|
+
" exit 1",
|
|
39809
|
+
" fi",
|
|
39810
|
+
' tmp="$(mktemp -d)"',
|
|
39811
|
+
` api="https://api.github.com/repos/vitessio/vitess/releases/tags/v${version3}"`,
|
|
39812
|
+
` url="$(curl -fsSL "$api" | grep -oE '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]+vitess-[0-9][^"]*\\.tar\\.gz"' | head -1 | sed -E 's/.*"(https[^"]+)"/\\1/')"`,
|
|
39813
|
+
' if [ -z "$url" ]; then echo "vtctldclient: no release tarball found for v' + version3 + '" >&2; rm -rf "$tmp"; exit 1; fi',
|
|
39814
|
+
` curl -fsSL "$url" | tar -xz -C "$tmp" --strip-components=2 --wildcards --occurrence=1 '*/bin/vtctldclient' || true`,
|
|
39815
|
+
' if [ ! -s "$tmp/vtctldclient" ]; then',
|
|
39816
|
+
' echo "vtctldclient: could not extract the binary from $url" >&2',
|
|
39817
|
+
' rm -rf "$tmp"; exit 1',
|
|
39818
|
+
" fi",
|
|
39819
|
+
` install -m 0755 "$tmp/vtctldclient" ${VTCTLDCLIENT_BIN}`,
|
|
39820
|
+
' rm -rf "$tmp"',
|
|
39821
|
+
` echo "installed vtctldclient ${version3}"`,
|
|
39822
|
+
"fi"
|
|
39823
|
+
];
|
|
39824
|
+
}
|
|
39825
|
+
function vtctldCommand(vitess) {
|
|
39826
|
+
if (!vitess.vtctldAddr)
|
|
39827
|
+
return null;
|
|
39828
|
+
return `${VTCTLDCLIENT_BIN} --server ${sh(vitess.vtctldAddr)}`;
|
|
39829
|
+
}
|
|
39830
|
+
function buildCreateKeyspaceScript(vitess, name, options = {}) {
|
|
39831
|
+
const base = vtctldCommand(vitess);
|
|
39832
|
+
if (!base)
|
|
39833
|
+
return null;
|
|
39834
|
+
const sharded = options.sharded ? " --sharded" : "";
|
|
39835
|
+
return [`${base} CreateKeyspace${sharded} ${sh(name)}`];
|
|
39836
|
+
}
|
|
39837
|
+
function buildApplyVSchemaScript(vitess, keyspace, vschemaJson) {
|
|
39838
|
+
const base = vtctldCommand(vitess);
|
|
39839
|
+
if (!base)
|
|
39840
|
+
return null;
|
|
39841
|
+
return [
|
|
39842
|
+
'vschema_tmp="$(mktemp)"',
|
|
39843
|
+
`cat > "$vschema_tmp" <<'TS_CLOUD_VSCHEMA_EOF'`,
|
|
39844
|
+
vschemaJson,
|
|
39845
|
+
"TS_CLOUD_VSCHEMA_EOF",
|
|
39846
|
+
`${base} ApplyVSchema --vschema-file "$vschema_tmp" ${sh(keyspace)}`,
|
|
39847
|
+
'rm -f "$vschema_tmp"'
|
|
39848
|
+
];
|
|
39849
|
+
}
|
|
39850
|
+
function buildApplySchemaScript(database, sql, strategy2 = "vitess") {
|
|
39851
|
+
return externalMysqlExec([`SET @@ddl_strategy='${strategy2}';`, sql.endsWith(";") ? sql : `${sql};`], database, VTGATE_DEFAULT_PORT);
|
|
39852
|
+
}
|
|
39853
|
+
function buildMigrationsScript(database, keyspace) {
|
|
39854
|
+
const statements = keyspace ? [`USE ${keyspace.replace(/[^A-Z0-9_]/gi, "")};`, "SHOW VITESS_MIGRATIONS;"] : ["SHOW VITESS_MIGRATIONS;"];
|
|
39855
|
+
const script = externalMysqlExec(statements, database, VTGATE_DEFAULT_PORT);
|
|
39856
|
+
script[0] = script[0].replace(" --skip-column-names", "");
|
|
39857
|
+
return script;
|
|
39858
|
+
}
|
|
39859
|
+
function parseMigrations(output) {
|
|
39860
|
+
const lines = output.split(`
|
|
39861
|
+
`).map((l) => l.trimEnd()).filter((l) => l.trim().length > 0);
|
|
39862
|
+
if (lines.length === 0)
|
|
39863
|
+
return [];
|
|
39864
|
+
const header3 = lines[0].split("\t").map((c) => c.trim());
|
|
39865
|
+
const col = (name) => header3.indexOf(name);
|
|
39866
|
+
const columns = {
|
|
39867
|
+
uuid: col("migration_uuid"),
|
|
39868
|
+
keyspace: col("keyspace"),
|
|
39869
|
+
shard: col("shard"),
|
|
39870
|
+
table: col("mysql_table"),
|
|
39871
|
+
status: col("migration_status"),
|
|
39872
|
+
strategy: col("strategy"),
|
|
39873
|
+
added: col("added_timestamp"),
|
|
39874
|
+
completed: col("completed_timestamp"),
|
|
39875
|
+
progress: col("progress")
|
|
39876
|
+
};
|
|
39877
|
+
if (columns.uuid === -1)
|
|
39878
|
+
return [];
|
|
39879
|
+
const rows = [];
|
|
39880
|
+
for (const line of lines.slice(1)) {
|
|
39881
|
+
const cells = line.split("\t");
|
|
39882
|
+
const at = (i) => i >= 0 ? (cells[i] ?? "").trim() : "";
|
|
39883
|
+
const uuid = at(columns.uuid);
|
|
39884
|
+
if (!uuid)
|
|
39885
|
+
continue;
|
|
39886
|
+
rows.push({
|
|
39887
|
+
uuid,
|
|
39888
|
+
keyspace: at(columns.keyspace),
|
|
39889
|
+
shard: at(columns.shard),
|
|
39890
|
+
table: at(columns.table),
|
|
39891
|
+
status: at(columns.status),
|
|
39892
|
+
strategy: at(columns.strategy),
|
|
39893
|
+
added: at(columns.added),
|
|
39894
|
+
completed: at(columns.completed),
|
|
39895
|
+
progress: at(columns.progress)
|
|
39896
|
+
});
|
|
39897
|
+
}
|
|
39898
|
+
return rows;
|
|
39899
|
+
}
|
|
39900
|
+
function failedMigrations(migrations2) {
|
|
39901
|
+
return migrations2.filter((m) => m.status.toLowerCase() === "failed");
|
|
39902
|
+
}
|
|
39903
|
+
function runningMigrations(migrations2) {
|
|
39904
|
+
return migrations2.filter((m) => ["running", "queued", "ready"].includes(m.status.toLowerCase()));
|
|
39905
|
+
}
|
|
39906
|
+
function buildMigrationActionScript(database, uuid, action) {
|
|
39907
|
+
const verb = action.toUpperCase();
|
|
39908
|
+
return externalMysqlExec([`ALTER VITESS_MIGRATION '${uuid}' ${verb};`], database, VTGATE_DEFAULT_PORT);
|
|
39909
|
+
}
|
|
39910
|
+
function requireVitess(config6) {
|
|
39911
|
+
if (resolveDbEngine(config6) !== "vitess")
|
|
39912
|
+
return { ok: false, error: "The configured database engine is not Vitess." };
|
|
39913
|
+
return null;
|
|
39914
|
+
}
|
|
39915
|
+
function requireControlPlane(vitess) {
|
|
39916
|
+
if (!vitess.vtctldAddr) {
|
|
39917
|
+
return {
|
|
39918
|
+
ok: false,
|
|
39919
|
+
error: "No vtctld address is configured. Creating keyspaces and applying a VSchema are topology operations that vtgate cannot perform. " + "Set `infrastructure.appDatabase.vitess.vtctldAddr` (for example `vtctld.internal:15999`) and redeploy."
|
|
39920
|
+
};
|
|
39921
|
+
}
|
|
39922
|
+
return null;
|
|
39923
|
+
}
|
|
39924
|
+
async function installVtctldClient(config6, environment2) {
|
|
39925
|
+
const wrong = requireVitess(config6);
|
|
39926
|
+
if (wrong)
|
|
39927
|
+
return wrong;
|
|
39928
|
+
const vitess = resolveVitessConfig(config6);
|
|
39929
|
+
return runDb(config6, environment2, buildVtctldClientInstallScript(vitess.clientVersion), "ts-cloud vitess:install-client");
|
|
39930
|
+
}
|
|
39931
|
+
async function createKeyspace(config6, environment2, name, options = {}) {
|
|
39932
|
+
const wrong = requireVitess(config6);
|
|
39933
|
+
if (wrong)
|
|
39934
|
+
return { ...wrong, keyspace: name };
|
|
39935
|
+
if (!isValidKeyspaceName(name))
|
|
39936
|
+
return { ok: false, error: "Keyspace name must be a valid identifier.", keyspace: name };
|
|
39937
|
+
const vitess = resolveVitessConfig(config6);
|
|
39938
|
+
const missing = requireControlPlane(vitess);
|
|
39939
|
+
if (missing)
|
|
39940
|
+
return { ...missing, keyspace: name };
|
|
39941
|
+
const script = buildCreateKeyspaceScript(vitess, name, options);
|
|
39942
|
+
if (!script)
|
|
39943
|
+
return { ...requireControlPlane({}), keyspace: name };
|
|
39944
|
+
const r = await runDb(config6, environment2, script, `ts-cloud vitess:create-keyspace ${name}`);
|
|
39945
|
+
return { ...r, keyspace: name };
|
|
39946
|
+
}
|
|
39947
|
+
async function applyVSchema(config6, environment2, keyspace, vschemaJson) {
|
|
39948
|
+
const wrong = requireVitess(config6);
|
|
39949
|
+
if (wrong)
|
|
39950
|
+
return { ...wrong, keyspace };
|
|
39951
|
+
if (!isValidKeyspaceName(keyspace))
|
|
39952
|
+
return { ok: false, error: "Keyspace name must be a valid identifier.", keyspace };
|
|
39953
|
+
try {
|
|
39954
|
+
const parsed = JSON.parse(vschemaJson);
|
|
39955
|
+
if (!parsed || typeof parsed !== "object")
|
|
39956
|
+
return { ok: false, error: "The VSchema must be a JSON object.", keyspace };
|
|
39957
|
+
} catch (error2) {
|
|
39958
|
+
return { ok: false, error: `The VSchema is not valid JSON: ${error2?.message ?? error2}`, keyspace };
|
|
39959
|
+
}
|
|
39960
|
+
const vitess = resolveVitessConfig(config6);
|
|
39961
|
+
const missing = requireControlPlane(vitess);
|
|
39962
|
+
if (missing)
|
|
39963
|
+
return { ...missing, keyspace };
|
|
39964
|
+
const script = buildApplyVSchemaScript(vitess, keyspace, vschemaJson);
|
|
39965
|
+
if (!script)
|
|
39966
|
+
return { ...requireControlPlane({}), keyspace };
|
|
39967
|
+
const r = await runDb(config6, environment2, script, `ts-cloud vitess:apply-vschema ${keyspace}`);
|
|
39968
|
+
return { ...r, keyspace };
|
|
39969
|
+
}
|
|
39970
|
+
async function applySchemaChange(config6, environment2, sql) {
|
|
39971
|
+
const wrong = requireVitess(config6);
|
|
39972
|
+
if (wrong)
|
|
39973
|
+
return wrong;
|
|
39974
|
+
if (!sql.trim())
|
|
39975
|
+
return { ok: false, error: "No SQL was provided." };
|
|
39976
|
+
const vitess = resolveVitessConfig(config6);
|
|
39977
|
+
return runDb(config6, environment2, buildApplySchemaScript(resolveAppDatabase(config6), sql, vitess.ddlStrategy ?? "vitess"), "ts-cloud vitess:apply-schema");
|
|
39978
|
+
}
|
|
39979
|
+
async function listMigrations(config6, environment2, keyspace) {
|
|
39980
|
+
const wrong = requireVitess(config6);
|
|
39981
|
+
if (wrong)
|
|
39982
|
+
return { ...wrong, migrations: [], failed: [], running: [] };
|
|
39983
|
+
const r = await runDb(config6, environment2, buildMigrationsScript(resolveAppDatabase(config6), keyspace), "ts-cloud vitess:migrations");
|
|
39984
|
+
const migrations2 = r.ok && r.stdout ? parseMigrations(r.stdout) : [];
|
|
39985
|
+
return { ...r, migrations: migrations2, failed: failedMigrations(migrations2), running: runningMigrations(migrations2) };
|
|
39986
|
+
}
|
|
39987
|
+
async function actOnMigration(config6, environment2, uuid, action) {
|
|
39988
|
+
const wrong = requireVitess(config6);
|
|
39989
|
+
if (wrong)
|
|
39990
|
+
return { ...wrong, uuid };
|
|
39991
|
+
if (!isValidMigrationUuid(uuid))
|
|
39992
|
+
return { ok: false, error: "Migration UUID is not in the expected format.", uuid };
|
|
39993
|
+
const r = await runDb(config6, environment2, buildMigrationActionScript(resolveAppDatabase(config6), uuid, action), `ts-cloud vitess:migration ${action} ${uuid}`);
|
|
39994
|
+
return { ...r, uuid };
|
|
39995
|
+
}
|
|
39678
39996
|
|
|
39679
39997
|
// src/deploy/dashboard-policy.ts
|
|
39680
39998
|
var FAIL_CLOSED = {
|
|
@@ -39914,6 +40232,13 @@ var POLICIES = {
|
|
|
39914
40232
|
"DELETE /api/firewall": { capability: "fleet:manage" },
|
|
39915
40233
|
"GET /api/databases": { capability: "data:read" },
|
|
39916
40234
|
"POST /api/databases": { capability: "data:admin" },
|
|
40235
|
+
"GET /api/databases/vitess": { capability: "data:read" },
|
|
40236
|
+
"GET /api/databases/vitess/migrations": { capability: "data:read" },
|
|
40237
|
+
"POST /api/databases/vitess/migrations/action": { capability: "data:admin" },
|
|
40238
|
+
"POST /api/databases/vitess/keyspaces": { capability: "data:admin" },
|
|
40239
|
+
"POST /api/databases/vitess/vschema": { capability: "data:admin" },
|
|
40240
|
+
"POST /api/databases/vitess/schema": { capability: "data:admin" },
|
|
40241
|
+
"POST /api/databases/vitess/install-client": { capability: "data:admin" },
|
|
39917
40242
|
"GET /api/databases/backups": { capability: "backups:read" },
|
|
39918
40243
|
"POST /api/databases/backup": { capability: "backups:create" },
|
|
39919
40244
|
"POST /api/databases/users": { capability: "data:admin" },
|
|
@@ -48887,6 +49212,45 @@ data: ${JSON.stringify({ cursor: position, at: new Date().toISOString() })}
|
|
|
48887
49212
|
}, 422);
|
|
48888
49213
|
return json15({ ...await createDatabase(config6, environment2, name), name });
|
|
48889
49214
|
}
|
|
49215
|
+
if (url.pathname === "/api/databases/vitess" && req.method === "GET") {
|
|
49216
|
+
const topology2 = await describeVitess(config6, environment2);
|
|
49217
|
+
return json15({
|
|
49218
|
+
...topology2,
|
|
49219
|
+
unhealthy: unhealthyTablets(topology2.tablets),
|
|
49220
|
+
missingPrimary: shardsMissingPrimary(topology2)
|
|
49221
|
+
});
|
|
49222
|
+
}
|
|
49223
|
+
if (url.pathname === "/api/databases/vitess/migrations" && req.method === "GET") {
|
|
49224
|
+
const keyspace = url.searchParams.get("keyspace") ?? undefined;
|
|
49225
|
+
return json15(await listMigrations(config6, environment2, keyspace));
|
|
49226
|
+
}
|
|
49227
|
+
if (url.pathname === "/api/databases/vitess/migrations/action" && req.method === "POST") {
|
|
49228
|
+
const body = await readJsonBody(req);
|
|
49229
|
+
const uuid = String(body.uuid ?? "").trim();
|
|
49230
|
+
const action = String(body.action ?? "");
|
|
49231
|
+
if (!["retry", "cancel", "cleanup", "complete"].includes(action))
|
|
49232
|
+
return json15({ ok: false, error: "Unknown migration action." }, 422);
|
|
49233
|
+
return json15(await actOnMigration(config6, environment2, uuid, action));
|
|
49234
|
+
}
|
|
49235
|
+
if (url.pathname === "/api/databases/vitess/keyspaces" && req.method === "POST") {
|
|
49236
|
+
const body = await readJsonBody(req);
|
|
49237
|
+
const name = String(body.name ?? "").trim();
|
|
49238
|
+
const sharded = body.sharded === true;
|
|
49239
|
+
return json15(await createKeyspace(config6, environment2, name, { sharded }));
|
|
49240
|
+
}
|
|
49241
|
+
if (url.pathname === "/api/databases/vitess/vschema" && req.method === "POST") {
|
|
49242
|
+
const body = await readJsonBody(req);
|
|
49243
|
+
const keyspace = String(body.keyspace ?? "").trim();
|
|
49244
|
+
const vschema = typeof body.vschema === "string" ? body.vschema : JSON.stringify(body.vschema ?? null);
|
|
49245
|
+
return json15(await applyVSchema(config6, environment2, keyspace, vschema));
|
|
49246
|
+
}
|
|
49247
|
+
if (url.pathname === "/api/databases/vitess/schema" && req.method === "POST") {
|
|
49248
|
+
const body = await readJsonBody(req);
|
|
49249
|
+
const sql = String(body.sql ?? "");
|
|
49250
|
+
return json15(await applySchemaChange(config6, environment2, sql));
|
|
49251
|
+
}
|
|
49252
|
+
if (url.pathname === "/api/databases/vitess/install-client" && req.method === "POST")
|
|
49253
|
+
return json15(await installVtctldClient(config6, environment2));
|
|
48890
49254
|
if (url.pathname === "/api/databases/backups" && req.method === "GET")
|
|
48891
49255
|
return json15(await listDatabaseBackups(config6, environment2));
|
|
48892
49256
|
if (url.pathname === "/api/databases/backup" && req.method === "POST") {
|