fix(project-export-import): keep restoring dumps when file writes fail, check local networks correctly

This commit is contained in:
Narciso
2026-10-15 18:12:57 -04:00
parent 3f90bb9159
commit 8383f48bd0
4 changed files with 55 additions and 18 deletions
@@ -91,6 +91,7 @@ const executeImport = async (
...postApplyErrors,
...(await restoreDumps(ctx.db, snapshot, result, workDir, {
emit: (message) => updateImportJob(importId, { message }),
skipServiceIds: new Set(postApplyErrors.map((error) => error.serviceId)),
})),
];
updateImportJob(importId, { restoreErrors, deployCandidates });
@@ -885,7 +885,12 @@ export const validatePlan = async (
if (!row || row.organizationId !== organizationId) {
throw new Error("Not found in this organization");
}
if ((row.serverId ?? null) !== probeServerIdForRequirement(key)) {
const requirement = requirementByKey.get(key);
if (
requirement &&
(row.serverId ?? null) !==
networkDestServerId(requirement, serviceServerMap, plan.serverMap)
) {
throw new Error(
`Network ${row.name} is on a different server than the services that use it`,
);
@@ -141,20 +141,29 @@ const writeCertificateFiles = async (
plan: Plan,
result: TransferResult,
emit: Emit,
): Promise<void> => {
): Promise<RestoreError[]> => {
const errors: RestoreError[] = [];
for (const requirement of snapshot.requirements) {
if (requirement.kind !== "certificate" || !requirement.originalId) continue;
const resolution = plan.resolutions[requirementKey(requirement)];
if (resolution?.action !== "create") continue;
const newCertificateId = result.idMap.certificate?.[requirement.originalId];
if (!newCertificateId) continue;
const row = await db.query.certificates.findFirst({
where: (fields, { eq }) => eq(fields.certificateId, newCertificateId),
});
if (!row) continue;
await createCertificateFiles(row as Certificate);
emit(`Wrote certificate files for ${row.name}`);
try {
const row = await db.query.certificates.findFirst({
where: (fields, { eq }) => eq(fields.certificateId, newCertificateId),
});
if (!row) continue;
await createCertificateFiles(row as Certificate);
emit(`Wrote certificate files for ${row.name}`);
} catch (error) {
errors.push({
serviceId: newCertificateId,
error: safeErrorMessage(error),
});
}
}
return errors;
};
const writeRawTraefikConfig = async (
@@ -235,7 +244,8 @@ const writeFileMounts = async (
snapshot: Snapshot,
result: TransferResult,
emit: Emit,
): Promise<void> => {
): Promise<RestoreError[]> => {
const errors: RestoreError[] = [];
for (const service of snapshot.resources.services) {
const kind = service.kind as ServiceType;
const oldId = service[SERVICE_ID_FIELD[kind]] as string | undefined;
@@ -246,15 +256,24 @@ const writeFileMounts = async (
const ownerField = SERVICE_ID_FIELD[
kind
] as keyof typeof mounts.$inferSelect;
const fileMounts = await db.query.mounts.findMany({
where: (fields, { eq, and }) =>
and(eq(fields[ownerField], newId), eq(fields.type, "file")),
});
for (const mount of fileMounts) {
await createFileMount(mount.mountId);
emit(`Wrote mount file ${mount.filePath} for ${newId}`);
try {
const fileMounts = await db.query.mounts.findMany({
where: (fields, { eq, and }) =>
and(eq(fields[ownerField], newId), eq(fields.type, "file")),
});
for (const mount of fileMounts) {
await createFileMount(mount.mountId);
emit(`Wrote mount file ${mount.filePath} for ${newId}`);
}
} catch (error) {
errors.push({
serviceId: newId,
serviceName: (service.name as string | undefined) ?? newId,
error: safeErrorMessage(error),
});
}
}
return errors;
};
export const postApply = async (
@@ -337,8 +356,10 @@ export const postApply = async (
postApplyErrors.push(
...(await createImportedNetworks(snapshot, plan, result, emit)),
);
await writeCertificateFiles(db, snapshot, plan, result, emit);
await writeFileMounts(db, snapshot, result, emit);
postApplyErrors.push(
...(await writeCertificateFiles(db, snapshot, plan, result, emit)),
...(await writeFileMounts(db, snapshot, result, emit)),
);
return { deployCandidates, postApplyErrors };
};
@@ -180,6 +180,7 @@ export type RestoreDumpsOptions = {
emit?: Emit;
deploy?: Partial<Record<RestorableKind, DeployFn>>;
readiness?: ReadinessOptions;
skipServiceIds?: Set<string>;
};
export const restoreDumps = async (
@@ -228,6 +229,15 @@ export const restoreDumps = async (
});
continue;
}
if (opts.skipServiceIds?.has(newServiceId)) {
restoreErrors.push({
serviceId: newServiceId,
serviceName,
error:
"Dump not restored because the service's files could not be written",
});
continue;
}
try {
const row = await (