feat: enhance database and storage management with Longhorn support

This commit is contained in:
2026-09-02 14:07:14 +07:00 Unverified
parent 6c8c8e0c1b
commit 26c2d0f015
12 changed files with 632 additions and 133 deletions
+4 -3
View File
@@ -5,7 +5,7 @@ import {
DATABASE_PORT,
buildDatabaseUrl,
getComposePostgresClaims,
getRoleCredentials,
reconcilePostgresClaim,
} from "../lib/database";
import { toTable } from "../lib/format";
@@ -43,7 +43,8 @@ const creds = defineCommand({
const service = args._[0];
if (!service) throw new Error("Service name is required");
const claim = getComposePostgresClaims(await ctx().compose()).find(
const context = ctx();
const claim = getComposePostgresClaims(await context.compose()).find(
(entry) => entry.service === service,
);
if (!claim) {
@@ -52,7 +53,7 @@ const creds = defineCommand({
);
}
const credentials = await getRoleCredentials(claim.username);
const credentials = await reconcilePostgresClaim(context.project, claim);
const url = buildDatabaseUrl(claim, credentials);
console.log(
+52 -29
View File
@@ -2,6 +2,7 @@ import { defineCommand } from "citty";
import https from "node:https";
import { createLogger } from "../lib/logger";
import { core, kc } from "../lib/k8s";
import { delay, parseRetryAfter } from "../lib/k8s-http";
import {
getPodContainerName,
listManagedDeployments,
@@ -13,10 +14,6 @@ function formatError(error: unknown): string {
return String(error);
}
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function streamResponseLines(
stream: NodeJS.ReadableStream,
onLine: (line: string) => void,
@@ -49,7 +46,9 @@ async function followPodLogs(
const cluster = kc.getCurrentCluster();
if (!cluster) throw new Error("No currently active cluster");
const requestURL = new URL(`${cluster.server}/api/v1/namespaces/${namespace}/pods/${podName}/log`);
const requestURL = new URL(
`${cluster.server}/api/v1/namespaces/${namespace}/pods/${podName}/log`,
);
requestURL.searchParams.set("container", containerName);
requestURL.searchParams.set("follow", "true");
@@ -63,30 +62,49 @@ async function followPodLogs(
};
await kc.applyToHTTPSOptions(options);
await new Promise<void>((resolve, reject) => {
const request = https.request(options, async (response) => {
if ((response.statusCode ?? 0) < 200 || (response.statusCode ?? 0) > 299) {
const chunks: Buffer[] = [];
response.on("data", (chunk) => {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
});
response.on("end", () => {
reject(Buffer.concat(chunks).toString("utf8") || `HTTP ${response.statusCode}`);
});
return;
}
for (let attempt = 0; ; attempt += 1) {
const result = await new Promise<{
statusCode: number;
retryAfter?: string;
body?: string;
}>((resolve, reject) => {
const request = https.request(options, async (response) => {
const statusCode = response.statusCode ?? 0;
if (statusCode < 200 || statusCode > 299) {
const chunks: Buffer[] = [];
response.on("data", (chunk) => {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
});
response.on("end", () => {
resolve({
statusCode,
retryAfter: response.headers["retry-after"],
body: Buffer.concat(chunks).toString("utf8"),
});
});
return;
}
try {
await streamResponseLines(response, writeLine);
resolve();
} catch (error) {
reject(error);
}
try {
await streamResponseLines(response, writeLine);
resolve({ statusCode });
} catch (error) {
reject(error);
}
});
request.on("error", reject);
request.end();
});
request.on("error", reject);
request.end();
});
if (result.statusCode >= 200 && result.statusCode <= 299) return;
if (result.statusCode !== 429 || attempt >= 3) {
throw new Error(result.body || `HTTP ${result.statusCode}`);
}
const retryAfter = parseRetryAfter(result.retryAfter);
await delay(retryAfter ?? 250 * 2 ** attempt, signal);
}
}
async function followDeploymentLogs(name: string, signal: AbortSignal) {
@@ -98,7 +116,11 @@ async function followDeploymentLogs(name: string, signal: AbortSignal) {
for (const pod of pods) {
const podName = pod.metadata?.name;
if (!podName || activePods.has(podName) || pod.status?.phase !== "Running") {
if (
!podName ||
activePods.has(podName) ||
pod.status?.phase !== "Running"
) {
continue;
}
@@ -118,7 +140,7 @@ async function followDeploymentLogs(name: string, signal: AbortSignal) {
});
}
await delay(2000);
await delay(2000, signal);
}
}
@@ -127,7 +149,8 @@ async function logDeployment(name: string, follow: boolean) {
if (!follow) {
const pods = await listPodsForDeployment(name);
if (pods.length === 0) throw new Error(`No pods found for deployment ${name}`);
if (pods.length === 0)
throw new Error(`No pods found for deployment ${name}`);
for (const pod of pods) {
const podName = pod.metadata?.name;