feat(microservices): manage code queue through v3s
This commit is contained in:
@@ -76,6 +76,14 @@ interface MicroserviceConfig {
|
||||
healthPath: string;
|
||||
timeoutMs: number;
|
||||
};
|
||||
deployment: {
|
||||
mode: "unidesk-direct" | "v3sctl-managed";
|
||||
adapterServiceId?: string;
|
||||
v3sServiceId?: string;
|
||||
namespace?: string;
|
||||
expectedNodeIds?: string[];
|
||||
activeNodeId?: string;
|
||||
};
|
||||
development: {
|
||||
providerId: string;
|
||||
sshPassthrough: boolean;
|
||||
@@ -306,6 +314,13 @@ function booleanFromRecord(value: Record<string, unknown>, key: string, path: st
|
||||
return field;
|
||||
}
|
||||
|
||||
function optionalStringFromRecord(value: Record<string, unknown>, key: string, path: string): string | undefined {
|
||||
const field = value[key];
|
||||
if (field === undefined) return undefined;
|
||||
if (typeof field !== "string" || field.length === 0) throw new Error(`${path}.${key} must be a non-empty string`);
|
||||
return field;
|
||||
}
|
||||
|
||||
function stringArrayFromRecord(value: Record<string, unknown>, key: string, path: string): string[] {
|
||||
const field = value[key];
|
||||
if (!Array.isArray(field) || field.some((item) => typeof item !== "string" || item.length === 0)) {
|
||||
@@ -314,6 +329,12 @@ function stringArrayFromRecord(value: Record<string, unknown>, key: string, path
|
||||
return field as string[];
|
||||
}
|
||||
|
||||
function optionalStringArrayFromRecord(value: Record<string, unknown>, key: string, path: string): string[] | undefined {
|
||||
const field = value[key];
|
||||
if (field === undefined) return undefined;
|
||||
return stringArrayFromRecord(value, key, path);
|
||||
}
|
||||
|
||||
function parseMicroserviceConfig(value: unknown, index: number): MicroserviceConfig {
|
||||
const path = `MICROSERVICES_JSON[${index}]`;
|
||||
const item = asRecord(value, path);
|
||||
@@ -321,6 +342,11 @@ function parseMicroserviceConfig(value: unknown, index: number): MicroserviceCon
|
||||
const backend = asRecord(item.backend, `${path}.backend`);
|
||||
const development = asRecord(item.development, `${path}.development`);
|
||||
const frontend = asRecord(item.frontend, `${path}.frontend`);
|
||||
const deployment = item.deployment === undefined ? undefined : asRecord(item.deployment, `${path}.deployment`);
|
||||
const deploymentMode = deployment === undefined ? "unidesk-direct" : stringFromRecord(deployment, "mode", `${path}.deployment`);
|
||||
if (deploymentMode !== "unidesk-direct" && deploymentMode !== "v3sctl-managed") {
|
||||
throw new Error(`${path}.deployment.mode must be unidesk-direct or v3sctl-managed`);
|
||||
}
|
||||
return {
|
||||
id: stringFromRecord(item, "id", path),
|
||||
name: stringFromRecord(item, "name", path),
|
||||
@@ -346,6 +372,16 @@ function parseMicroserviceConfig(value: unknown, index: number): MicroserviceCon
|
||||
healthPath: stringFromRecord(backend, "healthPath", `${path}.backend`),
|
||||
timeoutMs: numberFromRecord(backend, "timeoutMs", `${path}.backend`),
|
||||
},
|
||||
deployment: deployment === undefined
|
||||
? { mode: "unidesk-direct" }
|
||||
: {
|
||||
mode: deploymentMode,
|
||||
adapterServiceId: optionalStringFromRecord(deployment, "adapterServiceId", `${path}.deployment`),
|
||||
v3sServiceId: optionalStringFromRecord(deployment, "v3sServiceId", `${path}.deployment`),
|
||||
namespace: optionalStringFromRecord(deployment, "namespace", `${path}.deployment`),
|
||||
expectedNodeIds: optionalStringArrayFromRecord(deployment, "expectedNodeIds", `${path}.deployment`),
|
||||
activeNodeId: optionalStringFromRecord(deployment, "activeNodeId", `${path}.deployment`),
|
||||
},
|
||||
development: {
|
||||
providerId: stringFromRecord(development, "providerId", `${path}.development`),
|
||||
sshPassthrough: booleanFromRecord(development, "sshPassthrough", `${path}.development`),
|
||||
@@ -1672,10 +1708,12 @@ async function getMicroservices(): Promise<JsonValue[]> {
|
||||
return config.microservices.map((service) => {
|
||||
const node = nodes.find((item) => item.providerId === service.providerId) ?? null;
|
||||
const docker = dockerStatuses.find((item) => item.providerId === service.providerId) ?? null;
|
||||
const container = findContainer(docker?.dockerStatus ?? null, service.repository.containerName);
|
||||
const v3sManaged = isV3sctlManagedMicroservice(service);
|
||||
const container = v3sManaged ? null : findContainer(docker?.dockerStatus ?? null, service.repository.containerName);
|
||||
return {
|
||||
...service,
|
||||
runtime: {
|
||||
orchestrator: v3sManaged ? "v3sctl" : "unidesk-direct",
|
||||
providerStatus: node?.status ?? "missing",
|
||||
providerName: node?.name ?? service.providerId,
|
||||
providerLastHeartbeat: node?.lastHeartbeat ?? null,
|
||||
@@ -2996,6 +3034,10 @@ function canDirectProxyMicroservice(service: MicroserviceConfig): boolean {
|
||||
return service.providerId === "main-server";
|
||||
}
|
||||
|
||||
function isV3sctlManagedMicroservice(service: MicroserviceConfig): boolean {
|
||||
return service.deployment.mode === "v3sctl-managed" || service.backend.proxyMode === "v3sctl-adapter-http";
|
||||
}
|
||||
|
||||
async function directMicroserviceResponse(
|
||||
service: MicroserviceConfig,
|
||||
method: string,
|
||||
@@ -3055,6 +3097,28 @@ async function directMicroserviceResponse(
|
||||
}
|
||||
}
|
||||
|
||||
async function v3sctlAdapterMicroserviceResponse(
|
||||
service: MicroserviceConfig,
|
||||
method: string,
|
||||
targetPath: string,
|
||||
proxyOptions: { query: string; jsonArrayLimits: Record<string, JsonValue> },
|
||||
requestHeaders: Record<string, JsonValue>,
|
||||
bodyText: string,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<Response> {
|
||||
const adapterServiceId = service.deployment.adapterServiceId ?? "v3sctl-adapter";
|
||||
const adapter = microserviceById(adapterServiceId);
|
||||
if (adapter === null) {
|
||||
return jsonResponse({ ok: false, error: `v3sctl adapter microservice not found: ${adapterServiceId}`, serviceId: service.id }, 502);
|
||||
}
|
||||
if (adapter.id === service.id || isV3sctlManagedMicroservice(adapter)) {
|
||||
return jsonResponse({ ok: false, error: "v3sctl adapter must be a UniDesk-direct microservice", serviceId: service.id, adapterServiceId }, 500);
|
||||
}
|
||||
const v3sServiceId = service.deployment.v3sServiceId ?? service.id;
|
||||
const adapterTargetPath = `/api/services/${encodeURIComponent(v3sServiceId)}/proxy${targetPath}`;
|
||||
return fetchMicroserviceUpstreamResponse(adapter, method, adapterTargetPath, proxyOptions, requestHeaders, bodyText, abortSignal);
|
||||
}
|
||||
|
||||
async function fetchMicroserviceUpstreamResponse(
|
||||
service: MicroserviceConfig,
|
||||
method: string,
|
||||
@@ -3064,6 +3128,9 @@ async function fetchMicroserviceUpstreamResponse(
|
||||
bodyText: string,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<Response> {
|
||||
if (isV3sctlManagedMicroservice(service)) {
|
||||
return v3sctlAdapterMicroserviceResponse(service, method, targetPath, proxyOptions, requestHeaders, bodyText, abortSignal);
|
||||
}
|
||||
if (canDirectProxyMicroservice(service)) {
|
||||
return directMicroserviceResponse(service, method, targetPath, proxyOptions, requestHeaders, bodyText, abortSignal);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user