client.py 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import os
  2. from typing import TYPE_CHECKING
  3. if TYPE_CHECKING:
  4. from render import Render
  5. try:
  6. from .error import RenderError
  7. except ImportError:
  8. from error import RenderError
  9. def is_truenas_system():
  10. """Check if we're running on a TrueNAS system"""
  11. return "truenas" in os.uname().release
  12. # Import based on system detection
  13. if is_truenas_system():
  14. from truenas_api_client import Client as TrueNASClient
  15. try:
  16. # 25.04 and later
  17. from truenas_api_client.exc import ValidationErrors
  18. except ImportError:
  19. # 24.10 and earlier
  20. from truenas_api_client import ValidationErrors
  21. else:
  22. # Mock classes for non-TrueNAS systems
  23. class TrueNASClient:
  24. def call(self, *args, **kwargs):
  25. return None
  26. class ValidationErrors(Exception):
  27. def __init__(self, errors):
  28. self.errors = errors
  29. class Client:
  30. def __init__(self, render_instance: "Render"):
  31. self.client = TrueNASClient()
  32. self._render_instance = render_instance
  33. self._app_name: str = self._render_instance.values.get("ix_context", {}).get("app_name", "") or "unknown"
  34. def validate_ip_port_combo(self, ip: str, port: int) -> None:
  35. # Example of an error messages:
  36. # The port is being used by following services: 1) "0.0.0.0:80" used by WebUI Service
  37. # The port is being used by following services: 1) "0.0.0.0:9998" used by Applications ('$app_name' application)
  38. try:
  39. self.client.call("port.validate_port", f"render.{self._app_name}.schema", port, ip, None, True)
  40. except ValidationErrors as e:
  41. err_str = str(e)
  42. # If the IP:port combo appears more than once in the error message,
  43. # means that the port is used by more than one service/app.
  44. # This shouldn't happen in a well-configured system.
  45. # Notice that the ip portion is not included check,
  46. # because input might be a specific IP, but another service or app
  47. # might be using the same port on a wildcard IP
  48. if err_str.count(f':{port}" used by') > 1:
  49. raise RenderError(err_str) from None
  50. # If the error complains about the current app, we ignore it
  51. # This is to handle cases where the app is being updated or edited
  52. if f"Applications ('{self._app_name}' application)" in err_str:
  53. # During upgrade, we want to ignore the error if it is related to the current app
  54. return
  55. raise RenderError(err_str) from None
  56. except Exception:
  57. pass