Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions cli/scripts/um.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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.
"""
Expand Down Expand Up @@ -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__":
Expand Down
140 changes: 34 additions & 106 deletions cli/scripts/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down