feat: harden self-managed reconciliation
This commit is contained in:
+36
-6
@@ -138,15 +138,41 @@ export function getComposeS3Claims(compose: ComposeSpecification): S3Claim[] {
|
||||
return claims;
|
||||
}
|
||||
|
||||
function throwIfAborted(signal?: AbortSignal): void {
|
||||
if (!signal?.aborted) return;
|
||||
throw new Error("Workspace operation execution was cancelled");
|
||||
}
|
||||
|
||||
async function waitForInterval(
|
||||
delayMs: number,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
if (!signal) return new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
throwIfAborted(signal);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
signal.removeEventListener("abort", cancelWait);
|
||||
resolve();
|
||||
}, delayMs);
|
||||
const cancelWait = () => {
|
||||
clearTimeout(timer);
|
||||
reject(new Error("Workspace operation execution was cancelled"));
|
||||
};
|
||||
signal.addEventListener("abort", cancelWait, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
async function reconcileBuckets(
|
||||
project: string,
|
||||
claims: S3Claim[],
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
const buckets = new Map<string, S3Claim>();
|
||||
for (const claim of claims) buckets.set(claim.bucket, claim);
|
||||
|
||||
for (const claim of buckets.values()) {
|
||||
try {
|
||||
throwIfAborted(signal);
|
||||
await applyResource({
|
||||
apiVersion: GARAGE_API_VERSION,
|
||||
kind: "GarageBucket",
|
||||
@@ -177,12 +203,14 @@ async function reconcileBuckets(
|
||||
async function reconcileKeys(
|
||||
project: string,
|
||||
claims: S3Claim[],
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
const keys = new Map<string, S3Claim>();
|
||||
for (const claim of claims) keys.set(claim.key, claim);
|
||||
|
||||
for (const claim of keys.values()) {
|
||||
try {
|
||||
throwIfAborted(signal);
|
||||
await applyResource({
|
||||
apiVersion: GARAGE_API_VERSION,
|
||||
kind: "GarageKey",
|
||||
@@ -227,11 +255,13 @@ async function reconcileKeys(
|
||||
|
||||
export async function getS3Credentials(
|
||||
claim: S3Claim,
|
||||
signal?: AbortSignal,
|
||||
): Promise<Record<string, string>> {
|
||||
const deadline = Date.now() + SECRET_WAIT_TIMEOUT_MS;
|
||||
let lastPhase: string | undefined;
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
throwIfAborted(signal);
|
||||
const key = await readObject<GarageKeyStatus>({
|
||||
apiVersion: GARAGE_API_VERSION,
|
||||
kind: "GarageKey",
|
||||
@@ -274,9 +304,7 @@ export async function getS3Credentials(
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, SECRET_WAIT_INTERVAL_MS),
|
||||
);
|
||||
await waitForInterval(SECRET_WAIT_INTERVAL_MS, signal);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
@@ -287,17 +315,19 @@ export async function getS3Credentials(
|
||||
export async function reconcileS3Claims(
|
||||
project: string,
|
||||
compose: ComposeSpecification,
|
||||
signal?: AbortSignal,
|
||||
): Promise<Record<string, Record<string, string>>> {
|
||||
const claims = getComposeS3Claims(compose);
|
||||
if (claims.length === 0) return {};
|
||||
|
||||
await reconcileBuckets(project, claims);
|
||||
await reconcileKeys(project, claims);
|
||||
await reconcileBuckets(project, claims, signal);
|
||||
await reconcileKeys(project, claims, signal);
|
||||
|
||||
const environmentsByKey = new Map<string, Record<string, string>>();
|
||||
for (const claim of claims) {
|
||||
if (environmentsByKey.has(claim.key)) continue;
|
||||
environmentsByKey.set(claim.key, await getS3Credentials(claim));
|
||||
throwIfAborted(signal);
|
||||
environmentsByKey.set(claim.key, await getS3Credentials(claim, signal));
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
|
||||
Reference in New Issue
Block a user