fix: reconcile server deployments across repository views
ForgeFlow quality gate / quality (push) Canceled after 0s

This commit is contained in:
NuklearRabbit
2026-08-01 18:57:55 +02:00
parent 958d5b84d3
commit f7d6bc374f
11 changed files with 86 additions and 32 deletions
+31 -4
View File
@@ -384,15 +384,41 @@ function createUnraidInventoryMethods({
};
}
async scanServerInventory(serverId, repositories) {
async scanServerInventory(serverId, repositories, { autoLink = false } = {}) {
const { server, inventory, workloads } = await this.collectServerInventory(serverId, repositories);
const response = this.inventoryResponse(server, inventory, workloads);
let adopted = 0;
const adoptedLinks = [];
if (autoLink) {
const plan = this.reconciliationPlan(server, workloads, repositories, { autoLink: true });
if (plan.additions.length) await this.store.createRecoverySnapshot?.(`automatic-server-links-${serverId}`);
const linkedRepositories = new Set(workloads
.filter((workload) => workload.link?.repositoryFullName)
.map((workload) => String(workload.link.repositoryFullName).toLowerCase()));
for (const addition of plan.additions) {
const workload = workloads.find((item) => item.workloadId === addition.workloadId);
const repository = (repositories || []).find((item) => String(item.fullName).toLowerCase() === String(addition.repositoryFullName).toLowerCase());
const key = String(repository?.fullName || "").toLowerCase();
if (!workload || !repository || linkedRepositories.has(key)) continue;
const linkSource = addition.evidence === "exact-provenance" ? "automatic" : "automatic-runtime-identity";
const profile = this.profileFromWorkload(repository, server, workload, { linkSource, deploymentMode: "server-git" });
const saved = await this.store.saveDeploymentProfile(repository.fullName, profile);
await this.saveWorkloadState(saved, workload, server);
workload.status = "linked";
workload.link = { status: "linked", profileId: saved.id, repositoryFullName: repository.fullName, source: linkSource };
linkedRepositories.add(key);
adopted += 1;
adoptedLinks.push({ repositoryFullName: repository.fullName, profileId: saved.id, workloadId: workload.workloadId });
}
}
const response = this.inventoryResponse(server, inventory, workloads, { adopted });
await this.diagnostics?.info("unraid.workloads.scanned", {
serverId,
detected: response.detected,
linked: response.linked,
needsReview: response.needsReview,
readOnly: true,
adopted,
adoptedLinks,
readOnly: !autoLink,
});
return response;
}
@@ -428,6 +454,7 @@ function createUnraidInventoryMethods({
evidence: candidate.exact ? "exact-provenance" : "exact-runtime-identity",
impact: "Create a server-pull deployment profile; no container changes",
});
linkedRepositories.add(String(candidate.repositoryFullName).toLowerCase());
} else if (["suggested", "ambiguous"].includes(workload.status) || (workload.runtime?.running && workload.candidates?.length)) {
conflicts.push({
workloadId: workload.workloadId,
@@ -559,7 +586,7 @@ function createUnraidInventoryMethods({
}
async discoverServerWorkloads(serverId, repositories) {
return this.scanServerInventory(serverId, repositories);
return this.scanServerInventory(serverId, repositories, { autoLink: true });
}
async linkServerWorkload({ repository, serverId, workloadId, deploymentMode = "server-git", remoteFolder = "" }) {