fix: recover workspace update races
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ import { trust } from "./trust";
|
||||
export const main = defineCommand({
|
||||
meta: {
|
||||
name: "kuber",
|
||||
version: "2.3.2",
|
||||
version: "2.3.3",
|
||||
description: "Docker Compose -> K8s translation layer",
|
||||
},
|
||||
args: {
|
||||
|
||||
+53
-9
@@ -1,6 +1,7 @@
|
||||
import { defineCommand } from "citty";
|
||||
import { Listr, type ListrTaskWrapper } from "listr2";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import type { ComposeSpecification } from "../schema/docker.d";
|
||||
import type { KuberResource } from "../types";
|
||||
import {
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
|
||||
type Workspace = {
|
||||
metadata: { name: string; uid: string; resourceVersion: string };
|
||||
spec?: { source?: unknown; config?: unknown };
|
||||
};
|
||||
|
||||
type ResourceIdentity = {
|
||||
@@ -195,6 +197,27 @@ function workspaceInput(
|
||||
};
|
||||
}
|
||||
|
||||
function workspaceMatchesInput(
|
||||
workspace: Workspace,
|
||||
input: ReturnType<typeof workspaceInput>,
|
||||
): boolean {
|
||||
return (
|
||||
isDeepStrictEqual(workspace.spec?.source, input.source) &&
|
||||
isDeepStrictEqual(workspace.spec?.config, input.config)
|
||||
);
|
||||
}
|
||||
|
||||
function isWorkspaceUpdateAmbiguous(error: unknown): boolean {
|
||||
if (error instanceof KuberApiError) return error.status === 409;
|
||||
if (error instanceof DOMException && error.name === "AbortError")
|
||||
return false;
|
||||
if (error instanceof Error && error.name === "AbortError") return false;
|
||||
return (
|
||||
error instanceof TypeError ||
|
||||
(error instanceof Error && error.name === "TimeoutError")
|
||||
);
|
||||
}
|
||||
|
||||
export async function ensureWorkspace(
|
||||
project: string,
|
||||
compose: ComposeSpecification,
|
||||
@@ -203,21 +226,42 @@ export async function ensureWorkspace(
|
||||
): Promise<Workspace> {
|
||||
const path = `/workspaces/${encodeURIComponent(project)}`;
|
||||
const input = workspaceInput(compose, snapshot);
|
||||
const update = (workspace: Workspace) =>
|
||||
request<Workspace>(path, {
|
||||
method: "PUT",
|
||||
headers: { "if-match": `"${workspace.metadata.resourceVersion}"` },
|
||||
json: input,
|
||||
});
|
||||
const recover = async (originalError: unknown): Promise<Workspace> => {
|
||||
try {
|
||||
const fresh = await request<Workspace>(path);
|
||||
if (workspaceMatchesInput(fresh, input)) return fresh;
|
||||
return await update(fresh);
|
||||
} catch {
|
||||
throw originalError;
|
||||
}
|
||||
};
|
||||
let current: Workspace;
|
||||
try {
|
||||
current = await request<Workspace>(path);
|
||||
} catch (error) {
|
||||
if (!(error instanceof KuberApiError) || error.status !== 404) throw error;
|
||||
return request<Workspace>("/workspaces", {
|
||||
method: "POST",
|
||||
json: { id: project, ...input },
|
||||
});
|
||||
try {
|
||||
return await request<Workspace>("/workspaces", {
|
||||
method: "POST",
|
||||
json: { id: project, ...input },
|
||||
});
|
||||
} catch (createError) {
|
||||
if (!isWorkspaceUpdateAmbiguous(createError)) throw createError;
|
||||
return recover(createError);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return await update(current);
|
||||
} catch (updateError) {
|
||||
if (!isWorkspaceUpdateAmbiguous(updateError)) throw updateError;
|
||||
return recover(updateError);
|
||||
}
|
||||
return request<Workspace>(path, {
|
||||
method: "PUT",
|
||||
headers: { "if-match": `"${current.metadata.resourceVersion}"` },
|
||||
json: input,
|
||||
});
|
||||
}
|
||||
|
||||
function operationEnvironment(
|
||||
|
||||
Reference in New Issue
Block a user