""" Общие утилиты: извлечение UUID из ответов API Nubes. Проблема: Nubes API возвращает UUID инстанса/операции в разных форматах в зависимости от эндпоинта и фазы жизненного цикла: - POST /instances → {"instanceUid": "..."} (прямой ключ) - POST /instances → Location: "./uuid" (заголовок) - POST /instanceOperations → {"instanceOperationUid": "..."} (другой ключ) - GET /instanceOperations → {"instanceOperation": {"instanceOperationUid": "..."}} (вложенный) Эти две функции — единый способ достать UUID из ЛЮБОГО ответа. Раньше каждая точка вызова имела свою реализацию _find_uid / _uid_from_location. Теперь всё здесь — один источник правды. Используется: executor.py, api_test.py, scenario.py. """ def find_uid(resp): """Извлечь instanceUid или instanceOperationUid из dict-ответа API. Стратегия поиска (в порядке приоритета): 1. Верхний уровень — прямые ключи (стиль api_test.py): instanceOperationUid → instanceUid → uid → Uid 2. Вложенные dict-значения (стиль scenario.py): Для каждого значения-словаря проверяем instanceOperationUid → instanceUid Почему такой порядок: - instanceOperationUid приоритетнее — это ответ на POST /instanceOperations - instanceUid — ответ на POST /instances или GET /instances - uid / Uid — legacy-форматы, почти не встречаются Args: resp: dict — распарсенный JSON-ответ API (или что угодно). Returns: str или None — UUID (36 символов) или None если не нашли. """ # Защита: если передали не dict (например, list или str) — сразу None if not isinstance(resp, dict): return None # ── Уровень 1: прямые ключи на верхнем уровне ── # Пример: {"instanceUid": "abc-123", ...} # Проверяем по порядку: сначала специфичные, потом общие for key in ("instanceOperationUid", "instanceUid", "uid", "Uid"): v = resp.get(key) # isinstance(str) — отсекаем числа, None, пустые строки if isinstance(v, str) and v: return v # ── Уровень 2: вложенные dict-значения ── # Пример: {"instanceOperation": {"instanceOperationUid": "abc-123"}} # Перебираем ВСЕ значения resp, ищем вложенные словари for v in resp.values(): if isinstance(v, dict): # Внутри вложенного dict — те же ключи что на верхнем уровне uid = v.get("instanceOperationUid") or v.get("instanceUid") if uid: return uid # Ничего не нашли return None def uid_from_location(loc): """Извлечь UUID из Location-заголовка HTTP-ответа. Location — это HTTP-заголовок, который Nubes API возвращает при создании ресурса (201 Created). Форматы: - "./uuid" (относительный) - "/api/v1/svc/instances/uuid" (абсолютный путь) - "/api/v1/svc/instanceOperations/uuid" (полный путь) Алгоритм: 1. Убираем trailing slash. 2. Split по "/" — берём ПОСЛЕДНИЙ сегмент. 3. Проверяем что это похоже на UUID (>= 32 символа) и не ".". Args: loc: str или None — значение заголовка Location. Returns: str или None — UUID или None если не нашли. """ # Пустой Location → нечего извлекать if not loc: return None # Убираем trailing slash (напр. "./uuid/" → "./uuid") # Split по "/" → [".", "uuid"] или ["", "api", "v1", "svc", "instances", "uuid"] parts = str(loc).rstrip("/").split("/") # Последний сегмент — это UUID uid = parts[-1] # Проверки: # uid != "." — Location "./" даст пустой последний сегмент # len >= 32 — UUID всегда 36 символов (8-4-4-4-12), но на всякий случай >= 32 if uid and uid != "." and len(uid) >= 32: return uid return None