You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1 年之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. # imports - standard imports
  2. import atexit
  3. from contextlib import contextmanager
  4. from logging import Logger
  5. import os
  6. import pwd
  7. import sys
  8. # imports - third party imports
  9. import click
  10. # imports - module imports
  11. import bench
  12. from bench.bench import Bench
  13. from bench.commands import bench_command
  14. from bench.config.common_site_config import get_config
  15. from bench.utils import (
  16. check_latest_version,
  17. drop_privileges,
  18. find_parent_bench,
  19. get_env_xhiveframework_commands,
  20. get_cmd_output,
  21. is_bench_directory,
  22. is_dist_editable,
  23. is_root,
  24. log,
  25. setup_logging,
  26. get_cmd_from_sysargv,
  27. )
  28. from bench.utils.bench import get_env_cmd
  29. from importlib.util import find_spec
  30. # these variables are used to show dynamic outputs on the terminal
  31. dynamic_feed = False
  32. verbose = False
  33. is_envvar_warn_set = None
  34. from_command_line = False # set when commands are executed via the CLI
  35. bench.LOG_BUFFER = []
  36. change_uid_msg = "You should not run this command as root"
  37. src = os.path.dirname(__file__)
  38. SKIP_MODULE_TRACEBACK = ("click",)
  39. @contextmanager
  40. def execute_cmd(check_for_update=True, command: str = None, logger: Logger = None):
  41. if check_for_update:
  42. atexit.register(check_latest_version)
  43. try:
  44. yield
  45. except BaseException as e:
  46. return_code = getattr(e, "code", 1)
  47. if isinstance(e, Exception):
  48. click.secho(f"ERROR: {e}", fg="red")
  49. if return_code:
  50. logger.warning(f"{command} executed with exit code {return_code}")
  51. raise e
  52. def cli():
  53. setup_clear_cache()
  54. global from_command_line, bench_config, is_envvar_warn_set, verbose
  55. from_command_line = True
  56. command = " ".join(sys.argv)
  57. argv = set(sys.argv)
  58. is_envvar_warn_set = not (os.environ.get("BENCH_DEVELOPER") or os.environ.get("CI"))
  59. is_cli_command = len(sys.argv) > 1 and not argv.intersection({"src", "--version"})
  60. cmd_from_sys = get_cmd_from_sysargv()
  61. if "--verbose" in argv:
  62. verbose = True
  63. change_working_directory()
  64. logger = setup_logging()
  65. logger.info(command)
  66. bench_config = get_config(".")
  67. if is_cli_command:
  68. check_uid()
  69. change_uid()
  70. change_dir()
  71. if (
  72. is_envvar_warn_set
  73. and is_cli_command
  74. and not bench_config.get("developer_mode")
  75. and is_dist_editable(bench.PROJECT_NAME)
  76. ):
  77. log(
  78. "bench is installed in editable mode!\n\nThis is not the recommended mode"
  79. " of installation for production. Instead, install the package from PyPI"
  80. " with: `pip install xhiveframework-bench`\n",
  81. level=3,
  82. )
  83. in_bench = is_bench_directory()
  84. if (
  85. not in_bench
  86. and len(sys.argv) > 1
  87. and not argv.intersection(
  88. {"init", "find", "src", "drop", "get", "get-app", "--version"}
  89. )
  90. and not cmd_requires_root()
  91. ):
  92. log("Command not being executed in bench directory", level=3)
  93. if len(sys.argv) == 1 or sys.argv[1] == "--help":
  94. print(click.Context(bench_command).get_help())
  95. if in_bench:
  96. print(get_xhiveframework_help())
  97. return
  98. _opts = [x.opts + x.secondary_opts for x in bench_command.params]
  99. opts = {item for sublist in _opts for item in sublist}
  100. setup_exception_handler()
  101. # handle usages like `--use-feature='feat-x'` and `--use-feature 'feat-x'`
  102. if cmd_from_sys and cmd_from_sys.split("=", 1)[0].strip() in opts:
  103. bench_command()
  104. if cmd_from_sys in bench_command.commands:
  105. with execute_cmd(check_for_update=is_cli_command, command=command, logger=logger):
  106. bench_command()
  107. if in_bench:
  108. xhiveframework_cmd()
  109. bench_command()
  110. def check_uid():
  111. if cmd_requires_root() and not is_root():
  112. log("superuser privileges required for this command", level=3)
  113. sys.exit(1)
  114. def cmd_requires_root():
  115. if len(sys.argv) > 2 and sys.argv[2] in (
  116. "production",
  117. "sudoers",
  118. "lets-encrypt",
  119. "fonts",
  120. "print",
  121. "firewall",
  122. "ssh-port",
  123. "role",
  124. "fail2ban",
  125. "wildcard-ssl",
  126. ):
  127. return True
  128. if len(sys.argv) >= 2 and sys.argv[1] in (
  129. "patch",
  130. "renew-lets-encrypt",
  131. "disable-production",
  132. ):
  133. return True
  134. if len(sys.argv) > 2 and sys.argv[1] in ("install"):
  135. return True
  136. def change_dir():
  137. if os.path.exists("config.json") or "init" in sys.argv:
  138. return
  139. dir_path_file = "/etc/xhiveframework_bench_dir"
  140. if os.path.exists(dir_path_file):
  141. with open(dir_path_file) as f:
  142. dir_path = f.read().strip()
  143. if os.path.exists(dir_path):
  144. os.chdir(dir_path)
  145. def change_uid():
  146. if is_root() and not cmd_requires_root():
  147. xhiveframework_user = bench_config.get("xhiveframework_user")
  148. if xhiveframework_user:
  149. drop_privileges(uid_name=xhiveframework_user, gid_name=xhiveframework_user)
  150. os.environ["HOME"] = pwd.getpwnam(xhiveframework_user).pw_dir
  151. else:
  152. log(change_uid_msg, level=3)
  153. sys.exit(1)
  154. def app_cmd(bench_path="."):
  155. f = get_env_cmd("python", bench_path=bench_path)
  156. os.chdir(os.path.join(bench_path, "sites"))
  157. os.execv(f, [f] + ["-m", "xhiveframework.utils.bench_helper"] + sys.argv[1:])
  158. def xhiveframework_cmd(bench_path="."):
  159. f = get_env_cmd("python", bench_path=bench_path)
  160. os.chdir(os.path.join(bench_path, "sites"))
  161. os.execv(f, [f] + ["-m", "xhiveframework.utils.bench_helper", "xhiveframework"] + sys.argv[1:])
  162. def get_xhiveframework_commands():
  163. if not is_bench_directory():
  164. return set()
  165. return set(get_env_xhiveframework_commands())
  166. def get_xhiveframework_help(bench_path="."):
  167. python = get_env_cmd("python", bench_path=bench_path)
  168. sites_path = os.path.join(bench_path, "sites")
  169. try:
  170. out = get_cmd_output(
  171. f"{python} -m xhiveframework.utils.bench_helper get-xhiveframework-help", cwd=sites_path
  172. )
  173. return "\n\nFramework commands:\n" + out.split("Commands:")[1]
  174. except Exception:
  175. return ""
  176. def change_working_directory():
  177. """Allows bench commands to be run from anywhere inside a bench directory"""
  178. cur_dir = os.path.abspath(".")
  179. bench_path = find_parent_bench(cur_dir)
  180. bench.current_path = os.getcwd()
  181. bench.updated_path = bench_path
  182. if bench_path:
  183. os.chdir(bench_path)
  184. def setup_clear_cache():
  185. from copy import copy
  186. f = copy(os.chdir)
  187. def _chdir(*args, **kwargs):
  188. Bench.cache_clear()
  189. get_env_cmd.cache_clear()
  190. return f(*args, **kwargs)
  191. os.chdir = _chdir
  192. def setup_exception_handler():
  193. from traceback import format_exception
  194. from bench.exceptions import CommandFailedError
  195. def handle_exception(exc_type, exc_info, tb):
  196. if exc_type == CommandFailedError:
  197. print("".join(generate_exc(exc_type, exc_info, tb)))
  198. else:
  199. sys.__excepthook__(exc_type, exc_info, tb)
  200. def generate_exc(exc_type, exc_info, tb):
  201. TB_SKIP = [
  202. os.path.dirname(find_spec(module).origin) for module in SKIP_MODULE_TRACEBACK
  203. ]
  204. for tb_line in format_exception(exc_type, exc_info, tb):
  205. for skip_module in TB_SKIP:
  206. if skip_module not in tb_line:
  207. yield tb_line
  208. sys.excepthook = handle_exception