From 70314a009742e7b700d62ce8822c6b59ab1e0e70 Mon Sep 17 00:00:00 2001 From: host Date: Mon, 3 Aug 2026 00:21:36 +0300 Subject: [PATCH] =?UTF-8?q?truenas=5Ftools.py=20v2.1.2:=20=D0=B0=D0=B2?= =?UTF-8?q?=D1=82=D0=BE=D0=BC=D0=B0=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=D0=B9=20fallback=20POST=E2=86=92GET=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=20405=20+=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=BF=D1=83?= =?UTF-8?q?=D1=82=D0=B8=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- truenas_tools.py | 112 ++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 64 deletions(-) diff --git a/truenas_tools.py b/truenas_tools.py index 906010b..c1eb7de 100644 --- a/truenas_tools.py +++ b/truenas_tools.py @@ -1,64 +1,48 @@ -# ----------------------------------------------------------------- - # Каталоги и приложения (v2.1+) - # ----------------------------------------------------------------- - def discover_app_endpoints(self) -> str: - """ - Диагностический метод: проверяет разные эндпоинты API каталога приложений - и сообщает, какие из них реально работают на этой версии TrueNAS. - Полезно при обновлении truenas_tools.py или при ошибках в list_app_catalog. - """ - candidates_get = [ - "app/available", "app/available_apps", "app/list_apps", - "chart/release/names", "chart/release", "chart/release/list", - "catalog", "catalog?label=TRUENAS", "catalog/items", - "catalogs", "app/catalog", - ] - candidates_post = [ - ("app/available", {"catalog_name": "TRUENAS", "retrieve_all_trains": True}), - ("app/available", {}), - ("app.available", {"catalog_name": "TRUENAS"}), - ("chart/release/names", {"catalog_name": "TRUENAS"}), - ("chart.release.names", {}), - ("chart_release/names", {"catalog_name": "TRUENAS"}), - ("catalog/items", {"label": "TRUENAS"}), - ] - lines = ["🔍 ДИАГНОСТИКА ЭНДПОИНТОВ КАТАЛОГА", ""] - lines.append("=== GET ===") - for ep in candidates_get: - url = f"{self._base}/api/v2.0/{ep}" - try: - r = requests.get(url, headers=self._headers, timeout=self.valves.request_timeout, verify=self.valves.verify_ssl) - if r.status_code == 200: - body = r.text[:120].replace("\n", " ") - lines.append(f"✅ {ep:35s} HTTP 200 | {body}") - elif r.status_code == 404: - lines.append(f"❌ {ep:35s} HTTP 404") - elif r.status_code in (401, 403): - lines.append(f"🔑 {ep:35s} HTTP {r.status_code} (нет прав)") - else: - lines.append(f"⚠️ {ep:35s} HTTP {r.status_code}") - except Exception as e: - lines.append(f"💥 {ep:35s} ошибка: {type(e).__name__}") - lines.append("") - lines.append("=== POST ===") - for ep, payload in candidates_post: - url = f"{self._base}/api/v2.0/{ep}" - try: - r = requests.post(url, headers=self._headers, json=payload, timeout=self.valves.request_timeout, verify=self.valves.verify_ssl) - if r.status_code == 200: - body = r.text[:120].replace("\n", " ") - lines.append(f"✅ {ep:35s} HTTP 200 | {body}") - elif r.status_code == 404: - lines.append(f"❌ {ep:35s} HTTP 404") - elif r.status_code in (401, 403): - lines.append(f"🔑 {ep:35s} HTTP {r.status_code} (нет прав)") - else: - lines.append(f"⚠️ {ep:35s} HTTP {r.status_code}") - except Exception as e: - lines.append(f"💥 {ep:35s} ошибка: {type(e).__name__}") - lines.append("") - lines.append("ℹ️ Также можно посмотреть Swagger-документацию API:") - lines.append(f" {self._base}/api/docs/") - return "\n".join(lines) - - def list_catalogs(self) -> str: \ No newline at end of file +def _rest_request(self, method: str, endpoint: str, payload: Optional[dict] = None, timeout: Optional[int] = None) -> Optional[Any]: + method = method.upper() + if method not in ("POST", "PUT", "DELETE", "PATCH", "GET"): + self._last_error = f"_rest_request: неподдерживаемый метод {method}" + return None + url = f"{self._base}/api/v2.0/{endpoint}" + t = timeout if timeout is not None else self.valves.request_timeout + try: + r = requests.request(method, url, headers=self._headers, json=payload if payload is not None else {}, timeout=t, verify=self.valves.verify_ssl) + # v2.1.2: автоматический fallback POST → GET при 405 (Method Not Allowed). + # Это часто встречается в TrueNAS 25.10: некоторые эндпоинты каталога + # исторически были POST, но в новых версиях стали GET. + if r.status_code == 405 and method == "POST": + try: + r2 = requests.get(url, headers=self._headers, params=payload or {}, timeout=t, verify=self.valves.verify_ssl) + if r2.status_code in (200, 201, 202, 204): + if not r2.text: + return {"status": "ok", "http_code": r2.status_code, "via": "GET-fallback"} + try: + data = r2.json() + if isinstance(data, dict): + data["_via"] = "GET-fallback" + return data + except Exception: + return {"status": "ok", "text": r2.text, "http_code": r2.status_code, "_via": "GET-fallback"} + except Exception: + pass # вернёмся к исходной ошибке ниже + if r.status_code in (200, 201, 202, 204): + if not r.text: + return {"status": "ok", "http_code": r.status_code} + try: + return r.json() + except Exception: + return {"status": "ok", "text": r.text, "http_code": r.status_code} + self._last_error = f"{method} {endpoint} → HTTP {r.status_code}: {r.text[:400]}" + return None + except requests.exceptions.SSLError as e: + self._last_error = f"SSL-ошибка {method} {endpoint}: {e}" + return None + except requests.exceptions.ConnectionError as e: + self._last_error = f"Соединение {method} {endpoint}: {e}" + return None + except requests.exceptions.Timeout: + self._last_error = f"Таймаут {method} {endpoint} (> {t}с)" + return None + except Exception as e: + self._last_error = f"{method} {endpoint}: {type(e).__name__}: {e}" + return None \ No newline at end of file