Files
neon/scripts/sk_cleanup_tenants/script.py
Alexander Bayandin 4f4f787119 Update staging hostname (#7347)
## Problem

```
Could not resolve host: console.stage.neon.tech
```

## Summary of changes
- replace `console.stage.neon.tech` with `console-stage.neon.build`
2024-04-09 12:03:46 +01:00

133 lines
3.8 KiB
Python

import argparse
import logging
import os
import shutil
import sys
from pathlib import Path
import requests
level = logging.INFO
logging.basicConfig(
format="%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s",
datefmt="%Y-%m-%d:%H:%M:%S",
level=level,
)
parser = argparse.ArgumentParser()
parser.add_argument("--trash-dir", required=True, type=Path)
parser.add_argument("--dry-run", action="store_true")
parser.add_argument("--safekeeper-id", required=True, type=int)
parser.add_argument("--safekeeper-host", required=True, type=str)
args = parser.parse_args()
access_key = os.getenv("CONSOLE_API_TOKEN")
endpoint: str = "https://console-stage.neon.build/api"
trash_dir: Path = args.trash_dir
dry_run: bool = args.dry_run
logging.info(f"dry_run={dry_run}")
sk_id: int = args.safekeeper_id
sk_host: str = args.safekeeper_host
assert trash_dir.is_dir()
###
def console_get(rel_url):
r = requests.get(
f"{endpoint}{rel_url}",
headers={
"Authorization": f"Bearer {access_key}",
"Content-Type": "application/json",
"Accept": "application/json",
},
)
r.raise_for_status()
return r
def tenant_is_deleted_in_console(tenant_id):
r = console_get(f"/v1/admin/projects?search={tenant_id}&show_deleted=true")
r = r.json()
results = r["data"]
assert len(results) == 1, f"unexpected results len: {results}"
r = results[0]
assert r["tenant"] == tenant_id, f"tenant id doesn't match: {r}"
assert r["safekeepers"] is not None, f"safekeepers is None: {r}"
assert any(sk["id"] == sk_id for sk in r["safekeepers"]), f"safekeeper id not found: {r}"
assert "deleted" in r, f"{r}"
return r["deleted"] is True
def call_delete_tenant_api(tenant_id):
r = requests.delete(f"http://{sk_host}:7676/v1/tenant/{tenant_id}")
r.raise_for_status()
return r
def cleanup_tenant(tenant_id):
tenant_dir = Path(f"/storage/safekeeper/data/{tenant_id}")
if not tenant_dir.exists():
logging.info("tenant directory doesn't exist, assuming it has been cleaned already")
return
if not tenant_is_deleted_in_console(tenant_id):
logging.info("tenant is not deleted in console, skipping")
return
logging.info("assertions passed")
if dry_run:
return
logging.info("deleting tenant")
tenant_dir_in_trash = trash_dir / tenant_dir.relative_to("/")
tenant_dir_in_trash.parent.mkdir(parents=True, exist_ok=True)
assert not tenant_dir_in_trash.exists(), f"{tenant_dir_in_trash}"
assert tenant_dir_in_trash.parent.exists(), f"{tenant_dir_in_trash}"
# double-check
assert tenant_dir.exists(), f"{tenant_dir}"
assert tenant_dir.is_dir(), f"{tenant_dir}"
logging.info(f"copying {tenant_dir} to {tenant_dir_in_trash}")
shutil.copytree(src=tenant_dir, dst=tenant_dir_in_trash, symlinks=False, dirs_exist_ok=False)
logging.info(f"deleting {tenant_dir}")
call_delete_tenant_api(tenant_id)
logging.info("tenant is now deleted, checking that it's gone")
assert not tenant_dir.exists(), f"{tenant_dir}"
if os.path.exists("script.pid"):
logging.info(
f"script is already running, with pid={Path('script.pid').read_text()}. Terminate it first."
)
exit(1)
with open("script.pid", "w", encoding="utf-8") as f:
f.write(str(os.getpid()))
logging.info(f"started script.py, pid={os.getpid()}")
for line in sys.stdin:
tenant_id = line.strip()
try:
logging.info(f"start tenant {tenant_id}")
cleanup_tenant(tenant_id)
logging.info(f"done tenant {tenant_id}")
except KeyboardInterrupt:
print("KeyboardInterrupt exception is caught")
break
except: # noqa: E722
logging.exception(f"failed to clean up tenant {tenant_id}")
logging.info(f"finished script.py, pid={os.getpid()}")
os.remove("script.pid")