manage.py 17 KB

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