1ad3e0a11b1650d330a3d69ae7cbea771d647bc0 hiram Wed Apr 29 21:52:42 2026 -0700 adding galaxy cleanup process to release history and workflow metadata refs #31811 diff --git src/hg/utils/otto/userRequests/galaxyCleanup.py src/hg/utils/otto/userRequests/galaxyCleanup.py new file mode 100755 index 00000000000..82b3b29f29b --- /dev/null +++ src/hg/utils/otto/userRequests/galaxyCleanup.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# galaxyCleanup.py - delete a Galaxy invocation and purge its history +# +# usage: galaxyCleanup.py <profileJson> <invocationId> +# +# Use only after the workflow is complete and results have been +# downloaded -- purging the history is irreversible and frees the +# Galaxy-side disk space the workflow consumed. +# +# Reads galaxy_url and galaxy_user_key from the planemo profile JSON. +# Exits 0 on success (or if the invocation is already gone), non-zero +# on API error. + +import json +import sys +import urllib.error +import urllib.request + + +def apiCall(method, url, apiKey): + req = urllib.request.Request( + url, method=method, headers={"x-api-key": apiKey}) + try: + with urllib.request.urlopen(req) as r: + body = r.read() + return json.loads(body) if body else {} + except urllib.error.HTTPError as e: + if e.code == 404: + return None + raise + + +def main(): + if len(sys.argv) != 3: + sys.exit("usage: galaxyCleanup.py <profileJson> <invocationId>") + profileJson, invocationId = sys.argv[1], sys.argv[2] + + with open(profileJson) as f: + prof = json.load(f) + galaxyUrl = prof["galaxy_url"].rstrip("/") + apiKey = prof["galaxy_user_key"] + if not galaxyUrl or not apiKey: + sys.exit(f"ERROR: could not read galaxy_url or galaxy_user_key " + f"from {profileJson}") + + inv = apiCall("GET", + f"{galaxyUrl}/api/invocations/{invocationId}", apiKey) + if inv is None: + print(f"# invocation {invocationId} already gone", file=sys.stderr) + return + historyId = inv.get("history_id") + + # purge first -- this is the step that actually frees disk + if historyId: + try: + apiCall("DELETE", + f"{galaxyUrl}/api/histories/{historyId}?purge=true", + apiKey) + print(f"# purged history {historyId}", file=sys.stderr) + except urllib.error.HTTPError as e: + sys.exit(f"ERROR: failed to purge history {historyId}: {e}") + else: + print(f"# WARNING: invocation {invocationId} has no history_id", + file=sys.stderr) + + try: + apiCall("DELETE", + f"{galaxyUrl}/api/invocations/{invocationId}", apiKey) + print(f"# deleted invocation {invocationId}", file=sys.stderr) + except urllib.error.HTTPError as e: + # history is already purged, so disk is reclaimed -- the + # invocation row remaining is cosmetic + print(f"# WARNING: invocation delete returned {e.code}, " + f"history already purged", file=sys.stderr) + + +if __name__ == "__main__": + main()