fix: harden deployment discovery and preflight
ForgeFlow quality gate / quality (push) Canceled after 0s

This commit is contained in:
NuklearRabbit
2026-08-08 23:59:27 +02:00
parent f866b12fbf
commit 4c21616e72
18 changed files with 166 additions and 35 deletions
+11 -2
View File
@@ -212,10 +212,19 @@ function createUnraidAccessMethods({ shellQuote, path, bash, inventoryRemoteIden
add("commit-parity", "Gitea and server parity", branchSha && liveSha && branchSha === liveSha ? "pass" : branchSha && liveSha ? "warning" : "incomplete", branchSha && liveSha ? branchSha === liveSha ? "The exact Gitea commit is live." : `Live ${String(liveSha).slice(0, 12)} differs from Gitea ${String(branchSha).slice(0, 12)}.` : "Parity cannot be proven until both SHAs are available.", { branchSha, liveSha });
add("runtime", "Container runtime", running === true ? "pass" : running === false ? "fail" : "incomplete", running === true ? "The linked container is running." : running === false ? "The linked container is stopped." : "Runtime state has not been verified.");
add("health", "Runtime health", healthy === true ? "pass" : healthy === false ? "fail" : "incomplete", healthy === true ? "Runtime health passed." : healthy === false ? "Runtime health failed." : "No conclusive runtime health evidence is available.");
const deploymentCheckIds = new Set(["gitea-access", "remote-branch", "deploy-key-scope", "server-git-access", "server-inspection"]);
const deploymentBlockers = checks.filter((item) => deploymentCheckIds.has(item.id) && item.status !== "pass");
const deployReady = Boolean(branchSha) && deploymentBlockers.length === 0;
const failed = checks.some((item) => item.status === "fail");
const incomplete = checks.some((item) => ["warning", "incomplete", "unsupported"].includes(item.status));
const readiness = failed ? (checks.some((item) => item.id.includes("access") || item.id.includes("key")) ? "Access failed" : checks.some((item) => item.id === "runtime" || item.id === "health") ? "Runtime unhealthy" : "Configuration required") : incomplete ? (branchSha && liveSha && branchSha !== liveSha ? "Commit mismatch" : "Verification incomplete") : "Ready";
return { readiness, ready: readiness === "Ready" || readiness === "Commit mismatch", checkedAt: new Date().toISOString(), repository: repository.fullName, profileId, server: { id: server.id, name: server.name }, remotePath, branch: profile.branch, branchSha, liveSha, checks };
const readiness = deploymentBlockers.length
? "Access failed"
: failed
? "Deploy-ready; runtime unhealthy"
: incomplete
? (branchSha && liveSha && branchSha !== liveSha ? "Deployable update available" : "Deploy-ready; runtime verification incomplete")
: "Ready";
return { readiness, ready: deployReady, deployReady, deploymentBlockers, checkedAt: new Date().toISOString(), repository: repository.fullName, profileId, server: { id: server.id, name: server.name }, remotePath, branch: profile.branch, branchSha, liveSha, checks };
}
permissionTargets(profile, server, remotePath) {
+5 -1
View File
@@ -36,7 +36,11 @@ function createUnraidInventoryMethods({
disappeared=0
while IFS= read -r container_id; do
[ -n "$container_id" ] || continue
if inspect=$(docker inspect --format '{"id":{{json .Id}},"name":{{json .Name}},"image":{{json .Config.Image}},"imageId":{{json .Image}},"running":{{json .State.Running}},"status":{{json .State.Status}},"health":{{if .State.Health}}{{json .State.Health.Status}}{{else}}null{{end}},"labels":{{json .Config.Labels}},"ports":{{json .NetworkSettings.Ports}},"mounts":{{json .Mounts}},"networks":{{json .NetworkSettings.Networks}},"restartPolicy":{{json .HostConfig.RestartPolicy.Name}}}' "$container_id" 2>/dev/null); then
# Use Docker's own JSON document instead of a Go template. Accessing an
# absent .State.Health map key makes the formatted Docker inspect fail for
# every container without a healthcheck, which previously made those
# containers look as if they disappeared during the scan.
if inspect=$(docker inspect "$container_id" 2>/dev/null); then
printf 'C\\t%s\\n' "$(printf '%s' "$inspect" | base64 | tr -d '\\r\\n')"
else
disappeared=$((disappeared + 1))
+30 -1
View File
@@ -282,7 +282,36 @@ function createUnraidPreflightMethods({
});
}
}
if (deploymentMode === "server-git") {
const [owner, repo] = String(repository.fullName || "").split("/");
const deploymentFiles = profile.generatedCompose
? ["Dockerfile"]
: this.deploymentComposeFiles(profile);
try {
const existence = await Promise.all(deploymentFiles.map(async (filePath) => ({
filePath,
exists: await this.gitea.repositoryFileExists({ owner, repo, filePath, ref: targetSha }),
})));
const missing = existence.filter((item) => !item.exists).map((item) => item.filePath);
checks.push({
id: "gitea-deployment-files",
label: profile.generatedCompose ? "Dockerfile at Gitea commit" : "Compose files at Gitea commit",
status: missing.length ? "fail" : "pass",
detail: missing.length
? `Missing at exact commit ${targetSha.slice(0, 12)}: ${missing.join(", ")}.`
: `${deploymentFiles.join(", ")} verified at exact commit ${targetSha.slice(0, 12)}.`,
});
} catch (error) {
checks.push({
id: "gitea-deployment-files",
label: "Deployment files at Gitea commit",
status: "fail",
detail: error.message,
});
}
}
try {
const connection = await this.ssh.test(server.id, { trustOnFirstUse: false });
connectionCapabilities = connection.capabilities || {};