notes.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. from typing import TYPE_CHECKING
  2. if TYPE_CHECKING:
  3. from render import Render
  4. SHORT_LIVED = "short-lived"
  5. class Notes:
  6. def __init__(self, render_instance: "Render"):
  7. self._render_instance = render_instance
  8. self._app_name: str = ""
  9. self._app_train: str = ""
  10. self._warnings: list[str] = []
  11. self._deprecations: list[str] = []
  12. self._security: dict[str, list[str]] = {}
  13. self._header: str = ""
  14. self._body: str = ""
  15. self._footer: str = ""
  16. self._auto_set_app_name()
  17. self._auto_set_app_train()
  18. self._auto_set_header()
  19. self._auto_set_footer()
  20. def _is_enterprise_train(self):
  21. if self._app_train == "enterprise":
  22. return True
  23. def _auto_set_app_name(self):
  24. app_name = self._render_instance.values.get("ix_context", {}).get("app_metadata", {}).get("title", "")
  25. self._app_name = app_name or "<app_name>"
  26. def _auto_set_app_train(self):
  27. app_train = self._render_instance.values.get("ix_context", {}).get("app_metadata", {}).get("train", "")
  28. self._app_train = app_train or "<app_train>"
  29. def _auto_set_header(self):
  30. self._header = f"# {self._app_name}\n\n"
  31. def _auto_set_footer(self):
  32. url = "https://github.com/truenas/apps"
  33. if self._is_enterprise_train():
  34. url = "https://ixsystems.atlassian.net"
  35. footer = "## Bug Reports and Feature Requests\n\n"
  36. footer += "If you find a bug in this app or have an idea for a new feature, please file an issue at\n"
  37. footer += f"{url}\n\n"
  38. self._footer = footer
  39. def add_warning(self, warning: str):
  40. self._warnings.append(warning)
  41. def _prepend_warning(self, warning: str):
  42. self._warnings.insert(0, warning)
  43. def add_deprecation(self, deprecation: str):
  44. self._deprecations.append(deprecation)
  45. def set_body(self, body: str):
  46. self._body = body
  47. def scan_containers(self):
  48. for name, c in self._render_instance._containers.items():
  49. if self._security.get(name) is None:
  50. self._security[name] = []
  51. if c.restart._policy == "on-failure":
  52. self._security[name].append(SHORT_LIVED)
  53. if c._privileged:
  54. self._security[name].append("Is running with privileged mode enabled")
  55. run_as = c._user.split(":") if c._user else [-1, -1]
  56. if run_as[0] in ["0", -1]:
  57. self._security[name].append(f"Is running as {'root' if run_as[0] == '0' else 'unknown'} user")
  58. if run_as[1] in ["0", -1]:
  59. self._security[name].append(f"Is running as {'root' if run_as[1] == '0' else 'unknown'} group")
  60. if any(x in c._group_add for x in ("root", 0)):
  61. self._security[name].append("Is running with supplementary root group")
  62. if c._ipc_mode == "host":
  63. self._security[name].append("Is running with host IPC namespace")
  64. if c._pid_mode == "host":
  65. self._security[name].append("Is running with host PID namespace")
  66. if c._cgroup == "host":
  67. self._security[name].append("Is running with host cgroup namespace")
  68. if "no-new-privileges=true" not in c._security_opt.render():
  69. self._security[name].append("Is running without [no-new-privileges] security option")
  70. if c._tty:
  71. self._prepend_warning(
  72. f"Container [{name}] is running with a TTY, "
  73. "Logs will not appear correctly in the UI due to an [upstream bug]"
  74. "(https://github.com/docker/docker-py/issues/1394)"
  75. )
  76. self._security = {k: v for k, v in self._security.items() if v}
  77. def render(self):
  78. self.scan_containers()
  79. result = self._header
  80. if self._warnings:
  81. result += "## Warnings\n\n"
  82. for warning in self._warnings:
  83. result += f"- {warning}\n"
  84. result += "\n"
  85. if self._deprecations:
  86. result += "## Deprecations\n\n"
  87. for deprecation in self._deprecations:
  88. result += f"- {deprecation}\n"
  89. result += "\n"
  90. if self._security:
  91. result += "## Security\n\n"
  92. for c_name, security in self._security.items():
  93. if SHORT_LIVED in security and len(security) == 1:
  94. continue
  95. result += f"### Container: [{c_name}]"
  96. if SHORT_LIVED in security:
  97. result += "\n\n**This container is short-lived.**"
  98. result += "\n\n"
  99. for s in [s for s in security if s != "short-lived"]:
  100. result += f"- {s}\n"
  101. result += "\n"
  102. if self._body:
  103. result += self._body.strip() + "\n\n"
  104. result += self._footer
  105. return result