download-drivers: reuse an existing directory that differs only in case
Dell and GE manifests spell the same folder inconsistently - OptiPlex vs Optiplex. Windows does not care, but the Samba share sits on a case-sensitive filesystem, so a blind mkdir -p created a SECOND tree and the drivers split between them. That is exactly how the OptiPlex Micro 7020 pack went missing: the manifest asked for OptiPlex/D13MLK while the 3.2 GB zip sat in Optiplex/D13MLK. PESetup found no pack, logged a warning rather than an error, and the bay imaged with no network drivers - so DNS failed at first boot and bulk enrollment could not reach the CDN. Symptoms three steps from the cause. mkdir_ci walks the path one component at a time and reuses whatever is already there whatever its case, creating only genuinely new components. Callers must use the RETURNED path, since it may differ in case from the requested one. Falls back to plain mkdir -p rather than skipping a download if the resolve fails. Both spellings still exist in the live catalogues; scripts/lint-driver-catalogue.py reports a case mismatch as an error so the pair cannot silently drift again.
This commit is contained in:
@@ -68,6 +68,53 @@ def ssh_cmd(host, cmd):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Remote path resolver that is case-insensitive about EXISTING directories.
|
||||||
|
#
|
||||||
|
# Dell/GE manifests spell the same folder inconsistently ("OptiPlex" vs
|
||||||
|
# "Optiplex"). Windows does not care; the Samba share is backed by a
|
||||||
|
# case-sensitive Linux filesystem, so a blind "mkdir -p" creates a SECOND tree
|
||||||
|
# and the drivers split between them. That is exactly how the OptiPlex Micro
|
||||||
|
# 7020 package went missing: the manifest asked for OptiPlex/D13MLK while the
|
||||||
|
# 3.2 GB zip sat in Optiplex/D13MLK, so imaging installed no drivers at all -
|
||||||
|
# no NIC, no WiFi, and bulk enrollment could not reach the CDN.
|
||||||
|
#
|
||||||
|
# Walk the path one component at a time and reuse whatever is already there,
|
||||||
|
# whatever its case. Only genuinely new components get created.
|
||||||
|
CI_RESOLVE_SH = r"""
|
||||||
|
set -e
|
||||||
|
p="$1"; shift
|
||||||
|
for c in "$@"; do
|
||||||
|
[ -z "$c" ] && continue
|
||||||
|
if [ -d "$p/$c" ]; then
|
||||||
|
p="$p/$c"
|
||||||
|
else
|
||||||
|
m=$(ls -1 "$p" 2>/dev/null | awk -v c="$c" 'tolower($0)==tolower(c){print; exit}')
|
||||||
|
if [ -n "$m" ]; then p="$p/$m"; else mkdir -p "$p/$c"; p="$p/$c"; fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s' "$p"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def mkdir_ci(host, target_dir):
|
||||||
|
"""mkdir -p that reuses existing dirs differing only in case.
|
||||||
|
|
||||||
|
Returns the path that actually exists on the server, which may differ in
|
||||||
|
case from target_dir. Callers must use the RETURNED path."""
|
||||||
|
parts = [c for c in target_dir.split("/") if c]
|
||||||
|
if not parts:
|
||||||
|
return target_dir
|
||||||
|
base = "/" if target_dir.startswith("/") else "."
|
||||||
|
r = ssh_cmd(host, "bash -s -- '%s' %s <<'CIEOF'\n%s\nCIEOF" % (
|
||||||
|
base, " ".join("'%s'" % c.replace("'", "'\''") for c in parts), CI_RESOLVE_SH))
|
||||||
|
out = (r.stdout or "").strip()
|
||||||
|
if r.returncode != 0 or not out:
|
||||||
|
# Fall back to the old behaviour rather than skipping the download.
|
||||||
|
ssh_cmd(host, "mkdir -p '%s'" % target_dir)
|
||||||
|
return target_dir
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def verify_sha256(filepath, expected):
|
def verify_sha256(filepath, expected):
|
||||||
sha = hashlib.sha256()
|
sha = hashlib.sha256()
|
||||||
with open(filepath, "rb") as f:
|
with open(filepath, "rb") as f:
|
||||||
@@ -324,7 +371,9 @@ def process_download(args, url, filename, sha256, size, target_dir, label, tmpdi
|
|||||||
|
|
||||||
# Push zip to PXE server
|
# Push zip to PXE server
|
||||||
print(f" [{label}] Pushing to {target_dir}/{zip_name}...")
|
print(f" [{label}] Pushing to {target_dir}/{zip_name}...")
|
||||||
ssh_cmd(args.server, f"mkdir -p '{target_dir}'")
|
# Reuse an existing directory that differs only in case, or the drivers
|
||||||
|
# split across two trees and imaging silently installs none of them.
|
||||||
|
target_dir = mkdir_ci(args.server, target_dir)
|
||||||
r = subprocess.run([
|
r = subprocess.run([
|
||||||
"rsync", "-a",
|
"rsync", "-a",
|
||||||
"-e", f"sshpass -p {PXE_PASS} ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR",
|
"-e", f"sshpass -p {PXE_PASS} ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR",
|
||||||
|
|||||||
Reference in New Issue
Block a user