diff --git a/cli/scripts/um.py b/cli/scripts/um.py index ceff8e3a..8c359b6a 100755 --- a/cli/scripts/um.py +++ b/cli/scripts/um.py @@ -95,7 +95,7 @@ def install(component, active=True): # Trigger pre-check ONLY for Spock 5.0 artifacts if _SPOCK50_RE.match(component): - validate_spock_upgrade() # should sys.exit(1) on failure; otherwise just returns + validate_spock_upgrade(component) # should sys.exit(1) on failure; otherwise just returns # Common path (no duplication) if active not in (True, False): @@ -244,7 +244,7 @@ def verify_metadata(Project="", Stage="prod", IsCurrent=0): meta.pretty_sql(sql) -def validate_spock_upgrade(): +def validate_spock_upgrade(spock_component): """ Validate Spock↔PostgreSQL compatibility for an upcoming Spock 5 install. """ @@ -282,37 +282,38 @@ def validate_spock_upgrade(): if not pg_row: sys.exit("ERROR: No PostgreSQL version row found.") - pg_ver = pg_row[0] - spock_ver, spock_comp = (sp_row or (None, None)) + existing_pg_ver = pg_row[0] + existing_spock_ver, existing_spock_comp = (sp_row or (None, None)) # Already on Spock 5? No-op. - if spock_ver and ( - _SPOCK5_NAME_RE.match(spock_comp) or _SPOCK5_VER_RE.match(spock_ver) + if existing_spock_ver and ( + _SPOCK5_NAME_RE.match(existing_spock_comp) or _SPOCK5_VER_RE.match(existing_spock_ver) ): return 0 - + + # Trim "spock" off the front to get the version (e.g., "spock50" -> "50") + requested_spock_ver = spock_component + if requested_spock_ver and requested_spock_ver.lower().startswith("spock"): + requested_spock_ver = requested_spock_ver[5:] + # Downtime warning banner = "=" * 80 - if not spock_ver: - # Case 1: no existing Spock installed, run validation but don't print banner - try: - util.validate_spock_pg_compat('50', pg_ver) - except Exception as exc: - sys.exit(f"ERROR: Compatibility check failed: {exc}") - - elif spock_ver.startswith('4'): + if existing_spock_ver: # Case 2: Spock 4.x installed, this is a major upgrade print(f"\n{banner}") print("*** WARNING: This operation will cause downtime! ***") print(f"{banner}\n") - print(f"Detected Spock version {spock_ver} on PostgreSQL {pg_ver}") - try: - util.validate_spock_pg_compat(spock_ver, pg_ver) - except Exception as exc: - sys.exit(f"ERROR: Compatibility check failed: {exc}") + print(f"Detected existing Spock version {existing_spock_ver} on PostgreSQL {existing_pg_ver}") + try: + util.validate_spock_pg_compat(requested_spock_ver, existing_pg_ver) + except Exception as exc: + sys.exit(f"ERROR: Compatibility check failed: {exc}") + + if existing_spock_ver: print("Compatibility check passed.") + return 0 if __name__ == "__main__": diff --git a/cli/scripts/util.py b/cli/scripts/util.py index da2cf20e..c95915b4 100644 --- a/cli/scripts/util.py +++ b/cli/scripts/util.py @@ -124,122 +124,50 @@ def get_default_spock(pgv): return(DEFAULT_SPOCK) - - def validate_spock_pg_compat(spock_ver: str = None, pg_ver: str = None) -> None: - """ - Compatibility rules: - • If Spock < 5.0.0 ⇒ works with any supported PostgreSQL major. - • If Spock ≥ 5.0.0 ⇒ - – PG15 must be ≥ 15.13 - – PG16 must be ≥ 16.9 - – PG17 must be ≥ 17.5 - - Also supports shorthand Spock strings: - – "50" → "5.0.0", "40" → "4.0.0", etc. - """ - # 0) Fill in defaults if user didn’t pass anything - if not pg_ver: - pg_ver = DEFAULT_PG - else: - pg_ver = str(pg_ver) # ← force to string - + # --- defaults (keep yours as-is) --- + pg_ver = str(pg_ver) if pg_ver else str(DEFAULT_PG) if not spock_ver: - maj = int(pg_ver.split(".", 1)[0]) + try: + maj = int(pg_ver.split(".", 1)[0]) + except Exception: + maj = 17 spock_ver = DEFAULT_SPOCK_17 if maj == 17 else DEFAULT_SPOCK - else: - spock_ver = str(spock_ver) # ← force to string + spock_ver = str(spock_ver) + # --- parse Spock major w/o 'packaging' dependency --- + m_sp = re.fullmatch(r'(\d)(\d)$', spock_ver) # "50" -> "5.0.0" + if m_sp: + spock_ver = f"{int(m_sp.group(1))}.{int(m_sp.group(2))}.0" - # 0.5) Normalize two-digit shorthand (e.g. "50" → "5.0.0") - m = re.fullmatch(r'(\d)(\d)$', spock_ver) - if m: - spock_ver = f"{int(m.group(1))}.{int(m.group(2))}.0" - - # 1) Parse Spock version (abort on bad format) try: - spv = Version(spock_ver) - except ValueError: - exit_message(f"Invalid Spock version '{spock_ver}'. Aborting.", 1, isJSON) + spock_version_obj = Version.coerce(spock_ver) + except Exception as e: + exit_message(f"Invalid Spock version '{spock_ver}'.", 1, isJSON) - # 2) If Spock < 5 ⇒ compatible with any PG - if spv.major < 5: + if spock_version_obj.major < 5: return + + try: + pg_version_obj = Version.coerce(pg_ver) + except Exception as e: + exit_message(f"Invalid PostgreSQL version '{pg_ver}'.", 1, isJSON) - # — New block: handle pg_ver with “-1” or “-2” suffix - rev = None - rev_match = re.fullmatch(r'(\d+)\.(\d+)-(1|2)$', pg_ver) - if rev_match: - pg_major = int(rev_match.group(1)) - pg_patch = int(rev_match.group(2)) - rev = int(rev_match.group(3)) - - # reject revision “-1” on Spock ≥5 - if rev == 1: - exit_message( - f"Error: PostgreSQL {pg_major}.{pg_patch}-1 is not supported with Spock {spv}; " - "please use the “-2” revision instead.", - 1, - isJSON - ) - # for “-2”, we strip suffix and proceed with pg_major/pg_patch below - # end new block - - # 3) Spock ≥ 5 ⇒ enforce minimum‐patch for each PG major - minimum_patches = { - 15: 13, - 16: 9, - 17: 5, - } - - # 4) Extract PG major and patch (if not already set by rev_match) - if rev_match: - # pg_major, pg_patch are already set - pass - elif "." not in pg_ver: - # bare-major → use its minimum patch - try: - pg_major = int(pg_ver) - except ValueError: - exit_message(f"Invalid PostgreSQL version '{pg_ver}'. Aborting.", 1, isJSON) - if pg_major not in minimum_patches: - allowed = ", ".join(str(m) for m in minimum_patches) - exit_message( - f"Error: Spock {spv} supports only PostgreSQL majors {allowed}; " - f"you have {pg_major}. Aborting.", - 1, - isJSON - ) - pg_patch = minimum_patches[pg_major] - else: - parts = pg_ver.split(".", 2) - if len(parts) < 2: - exit_message(f"Invalid PostgreSQL version '{pg_ver}'. Aborting.", 1, isJSON) - try: - pg_major = int(parts[0]) - pg_patch = int(parts[1]) - except ValueError: - exit_message(f"Invalid PostgreSQL version '{pg_ver}'. Aborting.", 1, isJSON) - - # 5) Major must be supported - if pg_major not in minimum_patches: - allowed = ", ".join(str(m) for m in minimum_patches) - exit_message( - f"Error: Spock {spv} supports only PostgreSQL majors {allowed}; " - f"you have {pg_major}. Aborting.", - 1, - isJSON - ) + if not pg_version_obj.minor: + return + + min_version_thresholds = {15: Version.coerce("15.13-2"), 16: Version.coerce("16.9-2"), 17: Version.coerce("17.5-2")} + + if pg_version_obj.major in min_version_thresholds: + min_version_obj = min_version_thresholds[pg_version_obj.major] + min_version_build = min_version_obj.prerelease[0] if min_version_obj.prerelease else "0" + if pg_version_obj < min_version_obj: + exit_message( + f"Spock {spock_ver} requires PostgreSQL {pg_version_obj.major} >= {min_version_obj.major}.{min_version_obj.minor}-{min_version_build}. You provided {pg_ver}.", + 1, + isJSON, + ) - # 6) Enforce minimum‐patch - required = minimum_patches[pg_major] - if pg_patch < required: - exit_message( - f"Error: Spock {spv} requires PostgreSQL {pg_major}.{required} or newer; " - f"you have {pg_major}.{pg_patch}. Aborting.", - 1, - isJSON - ) def get_cpu_info(): try: import cpuinfo