manage.py 17 KB

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