manage.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. import os
  2. import io
  3. import sys
  4. import platform
  5. import shutil
  6. import time
  7. import subprocess
  8. import json
  9. import datetime
  10. import socket
  11. import re
  12. from threading import Thread
  13. from api.utils import shell_execute, docker, const
  14. from api.model.app import App
  15. from api.model.response import Response
  16. from api.model.running_info import RunningInfo
  17. from api.model.status_reason import StatusReason
  18. from api.utils.common_log import myLogger
  19. from redis import Redis
  20. from rq import Queue, Worker, Connection
  21. from rq.registry import StartedJobRegistry, FinishedJobRegistry, DeferredJobRegistry, FailedJobRegistry, \
  22. ScheduledJobRegistry, CanceledJobRegistry
  23. from api.exception.command_exception import CommandException
  24. # 指定 Redis 容器的主机名和端口
  25. redis_conn = Redis(host='websoft9-redis', port=6379)
  26. # 使用指定的 Redis 连接创建 RQ 队列
  27. q = Queue(connection=redis_conn)
  28. def AppList():
  29. myLogger.info_logger("Install app ...")
  30. ret = {}
  31. ret['ResponseData'] = {}
  32. app_id = app_name + "_" + customer_name
  33. ret['ResponseData'] = get_my_app()
  34. # 获取所有app的信息
  35. def get_my_app(customer_app_name):
  36. # get all info
  37. cmd = "docker compose ls -a --format json"
  38. output = shell_execute.execute_command_output_all(cmd)
  39. output_list = json.loads(output["result"])
  40. installed_list, has_add = get_apps_from_compose(output_list)
  41. installing_list = get_apps_from_queue()
  42. app_list = installed_list + installing_list
  43. find = False
  44. if customer_app_name != None:
  45. for app in app_list:
  46. if customer_app_name == app.customer_name:
  47. ret = app
  48. find = True
  49. break
  50. if not find:
  51. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "This App doesn't exist!", "")
  52. else:
  53. ret = app_list
  54. return ret
  55. # 获取具体某个app的信息
  56. def get_app_status(app_id):
  57. code, message = docker.check_app_id(app_id)
  58. customer_app_name = app_id.split('_')[1]
  59. if code == None:
  60. app = get_my_app(customer_app_name)
  61. # 将app_list 过滤出app_id的app,并缩减信息,使其符合文档的要求
  62. ret = {}
  63. ret['app_id'] = app.app_id
  64. app['status'] = app.status
  65. app['status_reason'] = app.status_reason
  66. else:
  67. raise CommandException(code, message, '')
  68. return ret
  69. def install_app(app_name, customer_name, app_version):
  70. myLogger.info_logger("Install app ...")
  71. ret = {}
  72. ret['ResponseData'] = {}
  73. app_id = app_name + "_" + customer_name
  74. ret['ResponseData']['app_id'] = app_id
  75. code, message = check_app(app_name, customer_name, app_version)
  76. if code == None:
  77. q.enqueue(install_app_delay, app_name, customer_name, app_version, job_id=app_id, timeout=3600)
  78. else:
  79. ret['Error'] = get_error_info(code, message, "")
  80. return ret
  81. def start_app(app_id):
  82. code, message = docker.check_app_id(app_id)
  83. if code == None:
  84. app_name = split_app_id(app_id)
  85. info, flag = app_exits_in_docker(app_id)
  86. if flag:
  87. app_path = info.split()[-1].rsplit('/', 1)[0]
  88. cmd = "docker compose -f " + app_path + "/docker-compose.yml start"
  89. shell_execute.execute_command_output_all(cmd)
  90. else:
  91. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  92. else:
  93. raise CommandException(code, message, '')
  94. def stop_app(app_id):
  95. code, message = docker.check_app_id(app_id)
  96. if code == None:
  97. app_name = split_app_id(app_id)
  98. info, flag = app_exits_in_docker(app_id)
  99. if flag:
  100. app_path = info.split()[-1].rsplit('/', 1)[0]
  101. cmd = "docker compose -f " + app_path + "/docker-compose.yml stop"
  102. shell_execute.execute_command_output_all(cmd)
  103. else:
  104. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  105. else:
  106. raise CommandException(code, message, "")
  107. def restart_app(app_id):
  108. code, message = docker.check_app_id(app_id)
  109. if code == None:
  110. app_name = split_app_id(app_id)
  111. info, flag = app_exits_in_docker(app_id)
  112. if flag:
  113. app_path = info.split()[-1].rsplit('/', 1)[0]
  114. cmd = "docker compose -f " + app_path + "/docker-compose.yml restart"
  115. shell_execute.execute_command_output_all(cmd)
  116. else:
  117. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  118. else:
  119. raise CommandException(code, message, "")
  120. def delete_app_failedjob(app_id):
  121. myLogger.info_logger("delete_app_failedjob")
  122. def uninstall_app(app_id):
  123. code, message = docker.check_appid_include_rq(app_id)
  124. if code == None:
  125. app_name = split_app_id(app_id)
  126. info, code_exist = app_exits_in_docker(app_id)
  127. if code_exist:
  128. app_path = info.split()[-1].rsplit('/', 1)[0]
  129. cmd = "docker compose -f " + app_path + "/docker-compose.yml down -v"
  130. lib_path = '/data/library/apps/' + app_name
  131. if app_path != lib_path:
  132. cmd = cmd + " && sudo rm -rf " + app_path
  133. shell_execute.execute_command_output_all(cmd)
  134. else:
  135. delete_app_failedjob(app_id)
  136. else:
  137. raise CommandException(code, message, "")
  138. return ret
  139. def check_app(app_name, customer_name, app_version):
  140. message = ""
  141. code = None
  142. app_id = app_name + "-" + customer_name
  143. if app_name == None:
  144. code = const.ERROR_CLIENT_PARAM_BLANK
  145. message = "app_name is null"
  146. elif customer_name == None:
  147. code = const.ERROR_CLIENT_PARAM_BLANK
  148. message = "customer_name is null"
  149. elif app_version == None:
  150. code = const.ERROR_CLIENT_PARAM_BLANK
  151. message = "app_version is null"
  152. elif not docker.check_app_websoft9(app_name):
  153. code = const.ERROR_CLIENT_PARAM_NOTEXIST
  154. message = "It is not support to install " + app_name
  155. elif re.match('^[a-z0-9]+$', customer_name) == None:
  156. code = const.ERROR_CLIENT_PARAM_Format
  157. message = "APP name can only be composed of numbers and lowercase letters"
  158. elif docker.check_directory("/data/apps/" + customer_name):
  159. code = const.ERROR_CLIENT_PARAM_REPEAT
  160. message = "Repeat installation: " + customer_name
  161. elif not docker.check_vm_resource(app_name):
  162. code = const.ERROR_SERVER_RESOURCE
  163. message = "Insufficient system resources (cpu, memory, disk space)"
  164. elif check_app_rq(app_id):
  165. code = const.ERROR_CLIENT_PARAM_REPEAT
  166. message = "Repeat installation: " + customer_name
  167. myLogger.info_logger("code="+code)
  168. return code, message
  169. def prepare_app(app_name, customer_name):
  170. library_path = "/data/library/apps/" + app_name
  171. install_path = "/data/apps/" + customer_name
  172. shell_execute.execute_command_output_all("cp -r " + library_path + " " + install_path)
  173. def install_app_delay(app_name, customer_name, app_version):
  174. job_id = app_name + "_" + customer_name
  175. try:
  176. code, message = check_app(app_name, customer_name, app_version)
  177. if code == None:
  178. prepare_app(app_name, customer_name)
  179. myLogger.info_logger("start JobID=" + job_id)
  180. # modify env
  181. env_path = "/data/apps/" + customer_name + "/.env"
  182. docker.modify_env(env_path, 'APP_NAME', customer_name)
  183. docker.modify_env(env_path, "APP_VERSION", app_version)
  184. # check port
  185. docker.check_app_compose(env_path)
  186. cmd = "cd /data/apps/" + customer_name + " && sudo docker compose pull && sudo docker compose up -d"
  187. output = shell_execute.execute_command_output_all(cmd)
  188. myLogger.info_logger("-------Install result--------")
  189. myLogger.info_logger(output["code"])
  190. myLogger.info_logger(output["result"])
  191. else:
  192. raise CommandException(code, message, "")
  193. except CommandException as ce:
  194. uninstall_app(job_id)
  195. raise CommandException(ce.code, ce.message, ce.detail)
  196. except Exception as e:
  197. myLogger.info_logger(customer_name + "install failed!")
  198. myLogger.error_logger(e)
  199. uninstall_app(job_id)
  200. raise CommandException(const.ERROR_SERVER_SYSTEM, "system original error", str(e))
  201. def app_exits_in_docker(app_id):
  202. customer_name = app_id.split('_')[1]
  203. app_name = app_id.split('_')[0]
  204. flag = False
  205. info = ""
  206. cmd = "docker compose ls -a | grep \'/" + app_name + "/\'"
  207. output = shell_execute.execute_command_output_all(cmd)
  208. if int(output["code"]) == 0:
  209. info = output["result"]
  210. app_path = info.split()[-1].rsplit('/', 1)[0]
  211. is_official = check_if_official_app(app_path + '/variables.json')
  212. if is_official:
  213. name = docker.read_var(app_path + '/variables.json', 'name')
  214. if name == app_name:
  215. flag = True
  216. elif app_name == customer_name:
  217. flag = True
  218. myLogger.info_logger("APP info: " + info)
  219. return info, flag
  220. def split_app_id(app_id):
  221. return app_id.split("_")[1]
  222. def get_apps_from_compose(output_list):
  223. ip_result = shell_execute.execute_command_output_all("curl ifconfig.me")
  224. ip = ip_result["result"]
  225. app_list = []
  226. has_add = []
  227. for app_info in output_list:
  228. volume = app_info["ConfigFiles"] # volume
  229. app_path = volume.rsplit('/', 1)[0]
  230. customer_name = volume.split('/')[-2]
  231. app_id = "" # app_id
  232. app_name = ""
  233. trade_mark = ""
  234. port = 0
  235. url = ""
  236. admin_url = ""
  237. image_url = ""
  238. user_name = ""
  239. password = ""
  240. official_app = False
  241. if customer_name in ['appmanage', 'nginxproxymanager',
  242. 'redis'] and app_path == '/data/apps/stackhub/docker/' + customer_name:
  243. continue
  244. # get code
  245. status = app_info["Status"].split("(")[0]
  246. if status == "running" or status == "exited" or status == "restarting":
  247. myLogger.info_logger("ok")
  248. elif status == "created":
  249. status = "failed"
  250. else:
  251. continue
  252. var_path = app_path + "/variables.json"
  253. official_app = check_if_official_app(var_path)
  254. if official_app:
  255. app_name = docker.read_var(var_path, 'name')
  256. app_id = app_name + "_" + customer_name # app_id
  257. # get trade_mark
  258. trade_mark = docker.read_var(var_path, 'trademark')
  259. image_url = get_Image_url(app_name)
  260. # get env info
  261. path = app_path + "/.env"
  262. # get port and url
  263. try:
  264. http_port = list(docker.read_env(
  265. path, "APP_HTTP_PORT").values())[0]
  266. port = int(http_port)
  267. easy_url = "http://" + ip + ":" + str(port)
  268. url = get_url(app_name, easy_url)
  269. admin_url = get_admin_url(app_name, url)
  270. except IndexError:
  271. try:
  272. db_port = list(docker.read_env(path, "APP_DB.*_PORT").values())[0]
  273. port = int(db_port)
  274. except IndexError:
  275. pass
  276. # get user_name
  277. try:
  278. user_name = list(docker.read_env(path, "APP_USER").values())[0]
  279. except IndexError:
  280. pass
  281. # get password
  282. try:
  283. password = list(docker.read_env(path, "POWER_PASSWORD").values())[0]
  284. except IndexError:
  285. pass
  286. has_add.append(customer_name)
  287. running_info = RunningInfo(port=port, compose_file=volume, url=url, admin_url=admin_url,
  288. user_name=user_name, password=password, default_domain="", set_domain="")
  289. status_reason = StatusReason(Code="", Message="", Detail="")
  290. app = App(app_id=app_id, name=app_name, customer_name=customer_name, trade_mark=trade_mark, status=status,
  291. status_reason=status_reason, official_app=official_app, image_url=image_url,
  292. running_info=running_info)
  293. app_list.append(app.dict())
  294. return app_list, has_add
  295. def check_if_official_app(var_path):
  296. if docker.check_directory(var_path):
  297. if docker.read_var(var_path, 'name') != "" and docker.read_var(var_path, 'trademark') != "" and docker.read_var(
  298. var_path, 'requirements') != "":
  299. requirements = docker.read_var(var_path, 'requirements')
  300. try:
  301. cpu = requirements['cpu']
  302. mem = requirements['memory']
  303. return True
  304. except:
  305. return False
  306. else:
  307. return False
  308. def check_app_rq(app_id):
  309. myLogger.info_logger("check_app_rq")
  310. for job in q.jobs:
  311. if app_id == job.id:
  312. return True
  313. started = StartedJobRegistry(queue=q)
  314. failed = FailedJobRegistry(queue=q)
  315. run_job_ids = started.get_job_ids()
  316. failed_jobs = failed.get_job_ids()
  317. if app_id in run_job_ids:
  318. return True
  319. if app_id in failed_jobs:
  320. return True
  321. return False
  322. def get_apps_from_queue():
  323. myLogger.info_logger("get queque apps...")
  324. # 获取 StartedJobRegistry 实例
  325. started = StartedJobRegistry(queue=q)
  326. finish = FinishedJobRegistry(queue=q)
  327. deferred = DeferredJobRegistry(queue=q)
  328. failed = FailedJobRegistry(queue=q)
  329. scheduled = ScheduledJobRegistry(queue=q)
  330. cancel = CanceledJobRegistry(queue=q)
  331. # 获取正在执行的作业 ID 列表
  332. run_job_ids = started.get_job_ids()
  333. finish_job_ids = finish.get_job_ids()
  334. wait_job_ids = deferred.get_job_ids()
  335. failed_jobs = failed.get_job_ids()
  336. scheduled_jobs = scheduled.get_job_ids()
  337. cancel_jobs = cancel.get_job_ids()
  338. myLogger.info_logger(q.jobs)
  339. myLogger.info_logger(run_job_ids)
  340. myLogger.info_logger(failed_jobs)
  341. myLogger.info_logger(cancel_jobs)
  342. myLogger.info_logger(wait_job_ids)
  343. myLogger.info_logger(finish_job_ids)
  344. myLogger.info_logger(scheduled_jobs)
  345. installing_list = []
  346. for job_id in run_job_ids:
  347. app = get_installing_app(job_id, 'installing', '""', "", "")
  348. installing_list.append(app)
  349. for job in q.jobs:
  350. app = get_installing_app(job.id, 'installing', "", "", "")
  351. installing_list.append(app)
  352. for job_id in failed_jobs:
  353. job = q.fetch_job(job_id)
  354. app = get_installing_app(job_id, 'failed', "", "", "")
  355. installing_list.append(app)
  356. return installing_list
  357. def get_installing_app(id, status, code, message, detail):
  358. app_name = id.split('_')[0]
  359. customer_name = id.split('_')[1]
  360. var_path = "/data/apps/" + customer_name + "/variables.json"
  361. trade_mark = docker.read_var(var_path, 'trademark')
  362. app_name = docker.read_var(var_path, 'name')
  363. image_url = get_Image_url(app_name)
  364. running_info = RunningInfo(port=0, compose_file="", url="", admin_url="",
  365. user_name="", password="", default_domain="", set_domain="")
  366. status_reason = StatusReason(Code=code, Message=message, Detail=detail)
  367. app = App(app_id=app_name + "_" + customer_name, name=app_name, customer_name=customer_name, trade_mark=trade_mark,
  368. status=status, status_reason=status_reason, official_app=True, image_url=image_url,
  369. running_info=running_info)
  370. return app
  371. def get_Image_url(app_name):
  372. image_url = "static/images/" + app_name + "-websoft9.png"
  373. return image_url
  374. def get_url(app_name, easy_url):
  375. url = easy_url
  376. if app_name == "joomla":
  377. url = easy_url + "/administrator"
  378. elif app_name == "other":
  379. url = easy_url + "/administrator"
  380. else:
  381. url = easy_url
  382. return url
  383. def get_admin_url(app_name, url):
  384. admin_url = ""
  385. if app_name == "wordpress":
  386. admin_url = url + "/wp-admin"
  387. elif app_name == "other":
  388. admin_url = url + "/admin"
  389. else:
  390. admin_url = ""
  391. return admin_url
  392. def get_error_info(code, message, detail):
  393. error = {}
  394. error['Code'] = code
  395. error['Message'] = message
  396. error['Detail'] = detail
  397. return error