您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. # imports - standard imports
  2. import contextlib
  3. import json
  4. import logging
  5. import os
  6. import re
  7. import shutil
  8. import subprocess
  9. import sys
  10. from functools import lru_cache
  11. from glob import glob
  12. from json.decoder import JSONDecodeError
  13. from pathlib import Path
  14. # imports - third party imports
  15. import click
  16. # imports - module imports
  17. import bench
  18. from bench.exceptions import PatchError, ValidationError
  19. from bench.utils import (
  20. exec_cmd,
  21. get_bench_cache_path,
  22. get_bench_name,
  23. get_cmd_output,
  24. log,
  25. which,
  26. )
  27. logger = logging.getLogger(bench.PROJECT_NAME)
  28. @lru_cache(maxsize=None)
  29. def get_env_cmd(cmd: str, bench_path: str = ".") -> str:
  30. exact_location = os.path.abspath(
  31. os.path.join(bench_path, "env", "bin", cmd.strip("*"))
  32. )
  33. if os.path.exists(exact_location):
  34. return exact_location
  35. # this supports envs' generated by patched virtualenv or venv (which may cause an extra 'local' folder to be created)
  36. existing_python_bins = glob(
  37. os.path.join(bench_path, "env", "**", "bin", cmd), recursive=True
  38. )
  39. if existing_python_bins:
  40. return os.path.abspath(existing_python_bins[0])
  41. return exact_location
  42. def get_venv_path(verbose=False, python="python3"):
  43. with open(os.devnull, "wb") as devnull:
  44. is_venv_installed = not subprocess.call(
  45. [python, "-m", "venv", "--help"], stdout=devnull
  46. )
  47. if is_venv_installed:
  48. return f"{python} -m venv"
  49. else:
  50. log("venv cannot be found", level=2)
  51. def update_node_packages(bench_path=".", apps=None, verbose=None):
  52. print("Updating node packages...")
  53. from distutils.version import LooseVersion
  54. from bench.utils.app import get_develop_version
  55. v = LooseVersion(get_develop_version("xhiveframework", bench_path=bench_path))
  56. # After rollup was merged, xhiveframework_version = 10.1
  57. # if develop_verion is 11 and up, only then install yarn
  58. if v < LooseVersion("11.x.x-develop"):
  59. update_npm_packages(bench_path, apps=apps, verbose=verbose)
  60. else:
  61. update_yarn_packages(bench_path, apps=apps, verbose=verbose)
  62. def install_python_dev_dependencies(bench_path=".", apps=None, verbose=False):
  63. import bench.cli
  64. from bench.bench import Bench
  65. verbose = bench.cli.verbose or verbose
  66. quiet_flag = "" if verbose else "--quiet"
  67. bench = Bench(bench_path)
  68. if isinstance(apps, str):
  69. apps = [apps]
  70. elif not apps:
  71. apps = bench.get_installed_apps()
  72. for app in apps:
  73. pyproject_deps = None
  74. app_path = os.path.join(bench_path, "apps", app)
  75. pyproject_path = os.path.join(app_path, "pyproject.toml")
  76. dev_requirements_path = os.path.join(app_path, "dev-requirements.txt")
  77. if os.path.exists(pyproject_path):
  78. pyproject_deps = _generate_dev_deps_pattern(pyproject_path)
  79. if pyproject_deps:
  80. bench.run(f"{bench.python} -m pip install {quiet_flag} --upgrade {pyproject_deps}")
  81. if not pyproject_deps and os.path.exists(dev_requirements_path):
  82. bench.run(
  83. f"{bench.python} -m pip install {quiet_flag} --upgrade -r {dev_requirements_path}"
  84. )
  85. def _generate_dev_deps_pattern(pyproject_path):
  86. try:
  87. from tomli import loads
  88. except ImportError:
  89. from tomllib import loads
  90. requirements_pattern = ""
  91. pyroject_config = loads(open(pyproject_path).read())
  92. with contextlib.suppress(KeyError):
  93. for pkg, version in pyroject_config["tool"]["bench"]["dev-dependencies"].items():
  94. op = "==" if "=" not in version else ""
  95. requirements_pattern += f"{pkg}{op}{version} "
  96. return requirements_pattern
  97. def update_yarn_packages(bench_path=".", apps=None, verbose=None):
  98. import bench.cli as bench_cli
  99. from bench.bench import Bench
  100. verbose = bench_cli.verbose or verbose
  101. bench = Bench(bench_path)
  102. apps = apps or bench.apps
  103. apps_dir = os.path.join(bench.name, "apps")
  104. # TODO: Check for stuff like this early on only??
  105. if not which("yarn"):
  106. print("Please install yarn using below command and try again.")
  107. print("`npm install -g yarn`")
  108. return
  109. for app in apps:
  110. app_path = os.path.join(apps_dir, app)
  111. if os.path.exists(os.path.join(app_path, "package.json")):
  112. click.secho(f"\nInstalling node dependencies for {app}", fg="yellow")
  113. yarn_install = "yarn install --check-files"
  114. if verbose:
  115. yarn_install += " --verbose"
  116. bench.run(yarn_install, cwd=app_path)
  117. def update_npm_packages(bench_path=".", apps=None, verbose=None):
  118. verbose = bench.cli.verbose or verbose
  119. npm_install = "npm install --verbose" if verbose else "npm install"
  120. apps_dir = os.path.join(bench_path, "apps")
  121. package_json = {}
  122. if not apps:
  123. apps = os.listdir(apps_dir)
  124. for app in apps:
  125. package_json_path = os.path.join(apps_dir, app, "package.json")
  126. if os.path.exists(package_json_path):
  127. with open(package_json_path) as f:
  128. app_package_json = json.loads(f.read())
  129. # package.json is usually a dict in a dict
  130. for key, value in app_package_json.items():
  131. if key not in package_json:
  132. package_json[key] = value
  133. else:
  134. if isinstance(value, dict):
  135. package_json[key].update(value)
  136. elif isinstance(value, list):
  137. package_json[key].extend(value)
  138. else:
  139. package_json[key] = value
  140. if package_json == {}:
  141. with open(os.path.join(os.path.dirname(__file__), "package.json")) as f:
  142. package_json = json.loads(f.read())
  143. with open(os.path.join(bench_path, "package.json"), "w") as f:
  144. f.write(json.dumps(package_json, indent=1, sort_keys=True))
  145. exec_cmd(npm_install, cwd=bench_path)
  146. def migrate_env(python, backup=False):
  147. import shutil
  148. from urllib.parse import urlparse
  149. from bench.bench import Bench
  150. bench = Bench(".")
  151. nvenv = "env"
  152. path = os.getcwd()
  153. python = which(python)
  154. pvenv = os.path.join(path, nvenv)
  155. if python.startswith(pvenv):
  156. # The supplied python version is in active virtualenv which we are about to nuke.
  157. click.secho(
  158. "Python version supplied is present in currently sourced virtual environment.\n"
  159. "`deactiviate` the current virtual environment before migrating environments.",
  160. fg="yellow",
  161. )
  162. sys.exit(1)
  163. # Clear Cache before Bench Dies.
  164. try:
  165. config = bench.conf
  166. rredis = urlparse(config["redis_cache"])
  167. redis = f"{which('redis-cli')} -p {rredis.port}"
  168. logger.log("Clearing Redis Cache...")
  169. exec_cmd(f"{redis} FLUSHALL")
  170. logger.log("Clearing Redis DataBase...")
  171. exec_cmd(f"{redis} FLUSHDB")
  172. except Exception:
  173. logger.warning("Please ensure Redis Connections are running or Daemonized.")
  174. # Backup venv: restore using `virtualenv --relocatable` if needed
  175. if backup:
  176. from datetime import datetime
  177. parch = os.path.join(path, "archived", "envs")
  178. os.makedirs(parch, exist_ok=True)
  179. source = os.path.join(path, "env")
  180. target = parch
  181. logger.log("Backing up Virtual Environment")
  182. stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  183. dest = os.path.join(path, str(stamp))
  184. os.rename(source, dest)
  185. shutil.move(dest, target)
  186. # Create virtualenv using specified python
  187. def _install_app(app):
  188. app_path = f"-e {os.path.join('apps', app)}"
  189. exec_cmd(f"{pvenv}/bin/python -m pip install --upgrade {app_path}")
  190. try:
  191. logger.log(f"Setting up a New Virtual {python} Environment")
  192. exec_cmd(f"{python} -m venv {pvenv}")
  193. # Install xhiveframework first
  194. _install_app("xhiveframework")
  195. for app in bench.apps:
  196. if str(app) != "xhiveframework":
  197. _install_app(app)
  198. logger.log(f"Migration Successful to {python}")
  199. except Exception:
  200. logger.warning("Python env migration Error", exc_info=True)
  201. raise
  202. def validate_upgrade(from_ver, to_ver, bench_path="."):
  203. if to_ver >= 6 and not which("npm") and not which("node") and not which("nodejs"):
  204. raise Exception("Please install nodejs and npm")
  205. def post_upgrade(from_ver, to_ver, bench_path="."):
  206. from bench.bench import Bench
  207. from bench.config import redis
  208. from bench.config.nginx import make_nginx_conf
  209. from bench.config.supervisor import generate_supervisor_config
  210. conf = Bench(bench_path).conf
  211. print("-" * 80 + f"Your bench was upgraded to version {to_ver}")
  212. if conf.get("restart_supervisor_on_update"):
  213. redis.generate_config(bench_path=bench_path)
  214. generate_supervisor_config(bench_path=bench_path)
  215. make_nginx_conf(bench_path=bench_path)
  216. print(
  217. "As you have setup your bench for production, you will have to reload"
  218. " configuration for nginx and supervisor. To complete the migration, please"
  219. " run the following commands:\nsudo service nginx restart\nsudo"
  220. " supervisorctl reload"
  221. )
  222. def patch_sites(bench_path="."):
  223. from bench.bench import Bench
  224. from bench.utils.system import migrate_site
  225. bench = Bench(bench_path)
  226. for site in bench.sites:
  227. try:
  228. migrate_site(site, bench_path=bench_path)
  229. except subprocess.CalledProcessError:
  230. raise PatchError
  231. def restart_supervisor_processes(bench_path=".", web_workers=False, _raise=False):
  232. from bench.bench import Bench
  233. bench = Bench(bench_path)
  234. conf = bench.conf
  235. cmd = conf.get("supervisor_restart_cmd")
  236. bench_name = get_bench_name(bench_path)
  237. if cmd:
  238. bench.run(cmd, _raise=_raise)
  239. else:
  240. sudo = ""
  241. try:
  242. supervisor_status = get_cmd_output("supervisorctl status", cwd=bench_path)
  243. except subprocess.CalledProcessError as e:
  244. if e.returncode == 127:
  245. log("restart failed: Couldn't find supervisorctl in PATH", level=3)
  246. return
  247. sudo = "sudo "
  248. supervisor_status = get_cmd_output("sudo supervisorctl status", cwd=bench_path)
  249. if not sudo and (
  250. "error: <class 'PermissionError'>, [Errno 13] Permission denied" in supervisor_status
  251. ):
  252. sudo = "sudo "
  253. supervisor_status = get_cmd_output("sudo supervisorctl status", cwd=bench_path)
  254. if web_workers and f"{bench_name}-web:" in supervisor_status:
  255. groups = [f"{bench_name}-web:\t"]
  256. elif f"{bench_name}-workers:" in supervisor_status:
  257. groups = [f"{bench_name}-web:", f"{bench_name}-workers:"]
  258. # backward compatibility
  259. elif f"{bench_name}-processes:" in supervisor_status:
  260. groups = [f"{bench_name}-processes:"]
  261. # backward compatibility
  262. else:
  263. groups = ["xhiveframework:"]
  264. for group in groups:
  265. failure = bench.run(f"{sudo}supervisorctl restart {group}", _raise=_raise)
  266. if failure:
  267. log(
  268. f"restarting supervisor group `{group}` failed. Use `bench restart` to retry.",
  269. level=3,
  270. )
  271. def restart_systemd_processes(bench_path=".", web_workers=False, _raise=True):
  272. bench_name = get_bench_name(bench_path)
  273. exec_cmd(
  274. f"sudo systemctl stop -- $(systemctl show -p Requires {bench_name}.target | cut"
  275. " -d= -f2)",
  276. _raise=_raise,
  277. )
  278. exec_cmd(
  279. f"sudo systemctl start -- $(systemctl show -p Requires {bench_name}.target |"
  280. " cut -d= -f2)",
  281. _raise=_raise,
  282. )
  283. def restart_process_manager(bench_path=".", web_workers=False):
  284. # only overmind has the restart feature, not sure other supported procmans do
  285. if which("overmind") and os.path.exists(os.path.join(bench_path, ".overmind.sock")):
  286. worker = "web" if web_workers else ""
  287. exec_cmd(f"overmind restart {worker}", cwd=bench_path)
  288. def build_assets(bench_path=".", app=None, using_cached=False):
  289. command = "bench build"
  290. if app:
  291. command += f" --app {app}"
  292. env = {"BENCH_DEVELOPER": "1"}
  293. if using_cached:
  294. env["USING_CACHED"] = "1"
  295. exec_cmd(command, cwd=bench_path, env=env)
  296. def handle_version_upgrade(version_upgrade, bench_path, force, reset, conf):
  297. from bench.utils import log, pause_exec
  298. if version_upgrade[0]:
  299. if force:
  300. log(
  301. """Force flag has been used for a major version change in Xhiveframework and it's apps.
  302. This will take significant time to migrate and might break custom apps.""",
  303. level=3,
  304. )
  305. else:
  306. print(
  307. f"""This update will cause a major version change in Xhiveframework/XhiveERP from {version_upgrade[1]} to {version_upgrade[2]}.
  308. This would take significant time to migrate and might break custom apps."""
  309. )
  310. click.confirm("Do you want to continue?", abort=True)
  311. if not reset and conf.get("shallow_clone"):
  312. log(
  313. """shallow_clone is set in your bench config.
  314. However without passing the --reset flag, your repositories will be unshallowed.
  315. To avoid this, cancel this operation and run `bench update --reset`.
  316. Consider the consequences of `git reset --hard` on your apps before you run that.
  317. To avoid seeing this warning, set shallow_clone to false in your common_site_config.json
  318. """,
  319. level=3,
  320. )
  321. pause_exec(seconds=10)
  322. if version_upgrade[0] or (not version_upgrade[0] and force):
  323. validate_upgrade(version_upgrade[1], version_upgrade[2], bench_path=bench_path)
  324. def update(
  325. pull: bool = False,
  326. apps: str = None,
  327. patch: bool = False,
  328. build: bool = False,
  329. requirements: bool = False,
  330. backup: bool = True,
  331. compile: bool = True,
  332. force: bool = False,
  333. reset: bool = False,
  334. restart_supervisor: bool = False,
  335. restart_systemd: bool = False,
  336. ):
  337. """command: bench update"""
  338. import re
  339. from bench import patches
  340. from bench.app import pull_apps
  341. from bench.bench import Bench
  342. from bench.config.common_site_config import update_config
  343. from bench.exceptions import CannotUpdateReleaseBench
  344. from bench.utils.app import is_version_upgrade
  345. from bench.utils.system import backup_all_sites
  346. bench_path = os.path.abspath(".")
  347. bench = Bench(bench_path)
  348. patches.run(bench_path=bench_path)
  349. conf = bench.conf
  350. if conf.get("release_bench"):
  351. raise CannotUpdateReleaseBench("Release bench detected, cannot update!")
  352. if not (pull or patch or build or requirements):
  353. pull, patch, build, requirements = True, True, True, True
  354. if apps and pull:
  355. apps = [app.strip() for app in re.split(",| ", apps) if app]
  356. else:
  357. apps = []
  358. validate_branch()
  359. version_upgrade = is_version_upgrade()
  360. handle_version_upgrade(version_upgrade, bench_path, force, reset, conf)
  361. conf.update({"maintenance_mode": 1, "pause_scheduler": 1})
  362. update_config(conf, bench_path=bench_path)
  363. if backup:
  364. print("Backing up sites...")
  365. backup_all_sites(bench_path=bench_path)
  366. if pull:
  367. print("Updating apps source...")
  368. pull_apps(apps=apps, bench_path=bench_path, reset=reset)
  369. if requirements:
  370. print("Setting up requirements...")
  371. bench.setup.requirements()
  372. if patch:
  373. print("Patching sites...")
  374. patch_sites(bench_path=bench_path)
  375. if build:
  376. print("Building assets...")
  377. bench.build()
  378. if version_upgrade[0] or (not version_upgrade[0] and force):
  379. post_upgrade(version_upgrade[1], version_upgrade[2], bench_path=bench_path)
  380. bench.reload(web=False, supervisor=restart_supervisor, systemd=restart_systemd)
  381. conf.update({"maintenance_mode": 0, "pause_scheduler": 0})
  382. update_config(conf, bench_path=bench_path)
  383. print(
  384. "_" * 80 + "\nBench: Deployment tool for Xhiveframework and Xhiveframework Applications"
  385. " (https://lab.membtech.com/xhiveframework/bench_new.git).\nOpen source depends on your contributions, so do"
  386. " give back by submitting bug reports, patches and fixes and be a part of the"
  387. " community :)"
  388. )
  389. def clone_apps_from(bench_path, clone_from, update_app=True):
  390. from bench.app import install_app
  391. print(f"Copying apps from {clone_from}...")
  392. subprocess.check_output(["cp", "-R", os.path.join(clone_from, "apps"), bench_path])
  393. node_modules_path = os.path.join(clone_from, "node_modules")
  394. if os.path.exists(node_modules_path):
  395. print(f"Copying node_modules from {clone_from}...")
  396. subprocess.check_output(["cp", "-R", node_modules_path, bench_path])
  397. def setup_app(app):
  398. # run git reset --hard in each branch, pull latest updates and install_app
  399. app_path = os.path.join(bench_path, "apps", app)
  400. # remove .egg-ino
  401. subprocess.check_output(["rm", "-rf", app + ".egg-info"], cwd=app_path)
  402. if update_app and os.path.exists(os.path.join(app_path, ".git")):
  403. remotes = subprocess.check_output(["git", "remote"], cwd=app_path).strip().split()
  404. if "upstream" in remotes:
  405. remote = "upstream"
  406. else:
  407. remote = remotes[0]
  408. print(f"Cleaning up {app}")
  409. branch = subprocess.check_output(
  410. ["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=app_path
  411. ).strip()
  412. subprocess.check_output(["git", "reset", "--hard"], cwd=app_path)
  413. subprocess.check_output(["git", "pull", "--rebase", remote, branch], cwd=app_path)
  414. install_app(app, bench_path, restart_bench=False)
  415. with open(os.path.join(clone_from, "sites", "apps.txt")) as f:
  416. apps = f.read().splitlines()
  417. for app in apps:
  418. setup_app(app)
  419. def remove_backups_crontab(bench_path="."):
  420. from crontab import CronTab
  421. from bench.bench import Bench
  422. logger.log("removing backup cronjob")
  423. bench_dir = os.path.abspath(bench_path)
  424. user = Bench(bench_dir).conf.get("xhiveframework_user")
  425. logfile = os.path.join(bench_dir, "logs", "backup.log")
  426. system_crontab = CronTab(user=user)
  427. backup_command = f"cd {bench_dir} && {sys.argv[0]} --verbose --site all backup"
  428. job_command = f"{backup_command} >> {logfile} 2>&1"
  429. system_crontab.remove_all(command=job_command)
  430. def set_mariadb_host(host, bench_path="."):
  431. update_common_site_config({"db_host": host}, bench_path=bench_path)
  432. def set_redis_cache_host(host, bench_path="."):
  433. update_common_site_config({"redis_cache": f"redis://{host}"}, bench_path=bench_path)
  434. def set_redis_queue_host(host, bench_path="."):
  435. update_common_site_config({"redis_queue": f"redis://{host}"}, bench_path=bench_path)
  436. def set_redis_socketio_host(host, bench_path="."):
  437. update_common_site_config({"redis_socketio": f"redis://{host}"}, bench_path=bench_path)
  438. def update_common_site_config(ddict, bench_path="."):
  439. filename = os.path.join(bench_path, "sites", "common_site_config.json")
  440. if os.path.exists(filename):
  441. with open(filename) as f:
  442. content = json.load(f)
  443. else:
  444. content = {}
  445. content.update(ddict)
  446. with open(filename, "w") as f:
  447. json.dump(content, f, indent=1, sort_keys=True)
  448. def validate_app_installed_on_sites(app, bench_path="."):
  449. print("Checking if app installed on active sites...")
  450. ret = check_app_installed(app, bench_path=bench_path)
  451. if ret is None:
  452. check_app_installed_legacy(app, bench_path=bench_path)
  453. else:
  454. return ret
  455. def check_app_installed(app, bench_path="."):
  456. try:
  457. out = subprocess.check_output(
  458. ["bench", "--site", "all", "list-apps", "--format", "json"],
  459. stderr=open(os.devnull, "wb"),
  460. cwd=bench_path,
  461. ).decode("utf-8")
  462. except subprocess.CalledProcessError:
  463. return None
  464. try:
  465. apps_sites_dict = json.loads(out)
  466. except JSONDecodeError:
  467. return None
  468. for site, apps in apps_sites_dict.items():
  469. if app in apps:
  470. raise ValidationError(f"Cannot remove, app is installed on site: {site}")
  471. def check_app_installed_legacy(app, bench_path="."):
  472. site_path = os.path.join(bench_path, "sites")
  473. for site in os.listdir(site_path):
  474. req_file = os.path.join(site_path, site, "site_config.json")
  475. if os.path.exists(req_file):
  476. out = subprocess.check_output(
  477. ["bench", "--site", site, "list-apps"], cwd=bench_path
  478. ).decode("utf-8")
  479. if re.search(r"\b" + app + r"\b", out):
  480. print(f"Cannot remove, app is installed on site: {site}")
  481. sys.exit(1)
  482. def validate_branch():
  483. from bench.bench import Bench
  484. from bench.utils.app import get_current_branch
  485. apps = Bench(".").apps
  486. installed_apps = set(apps)
  487. check_apps = {"xhiveframework", "xhiveerp"}
  488. intersection_apps = installed_apps.intersection(check_apps)
  489. for app in intersection_apps:
  490. branch = get_current_branch(app)
  491. if branch == "master":
  492. print(
  493. """'master' branch is renamed to 'version-11' since 'version-12' release.
  494. As of January 2020, the following branches are
  495. version Xhiveframework XhiveERP
  496. 11 version-11 version-11
  497. 12 version-12 version-12
  498. 13 version-13 version-13
  499. 14 develop develop
  500. Please switch to new branches to get future updates.
  501. To switch to your required branch, run the following commands: bench switch-to-branch [branch-name]"""
  502. )
  503. sys.exit(1)
  504. def cache_helper(clear=False, remove_app="", remove_key="") -> None:
  505. can_remove = bool(remove_key or remove_app)
  506. if not clear and not can_remove:
  507. cache_list()
  508. elif can_remove:
  509. cache_remove(remove_app, remove_key)
  510. elif clear:
  511. cache_clear()
  512. else:
  513. pass # unreachable
  514. def cache_list() -> None:
  515. from datetime import datetime
  516. tot_size = 0
  517. tot_items = 0
  518. printed_header = False
  519. for item in get_bench_cache_path("apps").iterdir():
  520. if item.suffix not in [".tar", ".tgz"]:
  521. continue
  522. stat = item.stat()
  523. size_mb = stat.st_size / 1_000_000
  524. created = datetime.fromtimestamp(stat.st_ctime)
  525. accessed = datetime.fromtimestamp(stat.st_atime)
  526. app = item.name.split("-")[0]
  527. tot_items += 1
  528. tot_size += stat.st_size
  529. compressed = item.suffix == ".tgz"
  530. if not printed_header:
  531. click.echo(
  532. f"{'APP':15} "
  533. f"{'FILE':25} "
  534. f"{'SIZE':>13} "
  535. f"{'COMPRESSED'} "
  536. f"{'CREATED':19} "
  537. f"{'ACCESSED':19} "
  538. )
  539. printed_header = True
  540. click.echo(
  541. f"{app:15} "
  542. f"{item.name:25} "
  543. f"{size_mb:10.3f} MB "
  544. f"{str(compressed):10} "
  545. f"{created:%Y-%m-%d %H:%M:%S} "
  546. f"{accessed:%Y-%m-%d %H:%M:%S} "
  547. )
  548. if tot_items:
  549. click.echo(f"Total size {tot_size / 1_000_000:.3f} MB belonging to {tot_items} items")
  550. else:
  551. click.echo("No cached items")
  552. def cache_remove(app: str = "", key: str = "") -> None:
  553. rem_items = 0
  554. rem_size = 0
  555. for item in get_bench_cache_path("apps").iterdir():
  556. if not should_remove_item(item, app, key):
  557. continue
  558. rem_items += 1
  559. rem_size += item.stat().st_size
  560. item.unlink(True)
  561. click.echo(f"Removed {item.name}")
  562. if rem_items:
  563. click.echo(f"Cleared {rem_size / 1_000_000:.3f} MB belonging to {rem_items} items")
  564. else:
  565. click.echo("No items removed")
  566. def should_remove_item(item: Path, app: str, key: str) -> bool:
  567. if item.suffix not in [".tar", ".tgz"]:
  568. return False
  569. name = item.name
  570. if app and key and name.startswith(f"{app}-{key[:10]}."):
  571. return True
  572. if app and name.startswith(f"{app}-"):
  573. return True
  574. if key and f"-{key[:10]}." in name:
  575. return True
  576. return False
  577. def cache_clear() -> None:
  578. cache_path = get_bench_cache_path("apps")
  579. tot_items = len(os.listdir(cache_path))
  580. if not tot_items:
  581. click.echo("No cached items")
  582. return
  583. tot_size = get_dir_size(cache_path)
  584. shutil.rmtree(cache_path)
  585. if tot_items:
  586. click.echo(f"Cleared {tot_size / 1_000_000:.3f} MB belonging to {tot_items} items")
  587. def get_dir_size(p: Path) -> int:
  588. return sum(i.stat(follow_symlinks=False).st_size for i in p.iterdir())