Fix CORE→SCALE compatibility for SMB and NFS share payloads

Strip CORE-only SMB share field (vuid) and NFS share fields
(paths, alldirs, quiet) that are rejected by the SCALE API.
Convert CORE's NFS paths list to the single path string SCALE expects.
Also include NFS paths in dry-run dataset existence checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 10:03:41 -05:00
parent 7d4921e71a
commit e094c05cae

View File

@@ -539,16 +539,33 @@ def list_archive_and_exit(tar_path: str) -> None:
# Read-only / server-generated fields that must NOT be sent on create/update
_SMB_SHARE_READONLY = frozenset({"id", "locked"})
_NFS_SHARE_READONLY = frozenset({"id", "locked"})
_SMB_CONFIG_READONLY = frozenset({"id", "server_sid"})
# CORE SMB share fields that do not exist in the SCALE API
_SMB_SHARE_CORE_EXTRAS = frozenset({
"vuid", # server-generated Time Machine UUID; SCALE sets this automatically
})
# CORE NFS share fields that do not exist in the SCALE API
_NFS_SHARE_CORE_EXTRAS = frozenset({
"paths", # CORE uses a list; SCALE uses a single "path" string (converted below)
"alldirs", # removed in SCALE
"quiet", # removed in SCALE
})
def _smb_share_payload(share: dict) -> dict:
return {k: v for k, v in share.items() if k not in _SMB_SHARE_READONLY}
exclude = _SMB_SHARE_READONLY | _SMB_SHARE_CORE_EXTRAS
return {k: v for k, v in share.items() if k not in exclude}
def _nfs_share_payload(share: dict) -> dict:
return {k: v for k, v in share.items() if k not in _NFS_SHARE_READONLY}
payload = {k: v for k, v in share.items()
if k not in {"id", "locked"} | _NFS_SHARE_CORE_EXTRAS}
# CORE stores export paths as a list under "paths"; SCALE expects a single "path" string.
if "path" not in payload and share.get("paths"):
payload["path"] = share["paths"][0]
return payload
def _smb_config_payload(config: dict) -> dict:
@@ -952,7 +969,10 @@ async def migrate_nfs_shares(
log.info(" Destination has %d existing NFS share(s).", len(existing_paths))
for share in shares:
path = share.get("path", "").rstrip("/")
# CORE archives store paths as a list; SCALE uses a single string.
core_paths = share.get("paths") or []
path = (share.get("path") or (core_paths[0] if core_paths else "")).rstrip("/")
all_paths = [p.rstrip("/") for p in (core_paths if core_paths else ([path] if path else []))]
log.info("── NFS export %r", path)
if path in existing_paths:
@@ -966,8 +986,7 @@ async def migrate_nfs_shares(
if dry_run:
log.info(" [DRY RUN] would create NFS export for %r", path)
summary.nfs_created += 1
if path:
summary.paths_to_create.append(path)
summary.paths_to_create.extend(all_paths)
continue
try: