manage.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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. # 查询某个正在安装的app的 具体状态:waiting(等待安装)pulling(拉取镜像)initializing(初始化)running(正常运行)
  73. def install_app_process(app_id):
  74. ret = {}
  75. ret['code'] = const.RETURN_FAIL
  76. ret['message'] = ""
  77. ret['status'] = ""
  78. app_name = split_app_id(app_id)
  79. if docker.check_app_id(app_id):
  80. info, code = if_app_exits(app_id)
  81. if code:
  82. var_path = "/data/apps/" + app_name + "/variables.json"
  83. real_name = docker.read_var(var_path, 'name')
  84. app_status = docker.get_process_perc(app_name, real_name)
  85. ret["code"] = const.RETURN_SUCCESS
  86. ret['message'] = "This app is installing."
  87. ret['status'] = app_status
  88. else:
  89. ret['message'] = "This app is not currently installed."
  90. else:
  91. ret['message'] = "AppID is not legal!"
  92. ret = ret.dict()
  93. return ret
  94. def install_app(app_name, customer_app_name, app_version):
  95. myLogger.info_logger("Install app ...")
  96. ret = {}
  97. ret['ResponseData'] = {}
  98. if app_name == None:
  99. ret['Error'] = setErrorInfo('Param.AppName.Blank', 'APP名称为空')
  100. elif customer_app_name == None:
  101. ret['Error'] = setErrorInfo('Param.CustomerAppName.Blank', '用户自定义APP名称为空')
  102. elif app_version == None:
  103. ret['Error'] = setErrorInfo('Param.AppVersion.Blank', '安装App的版本不能为空')
  104. else:
  105. app_id = app_name + "_" + customer_app_name
  106. ret['ResponseData']['app_id'] = app_id
  107. code, message = check_app(app_name, customer_app_name, app_version)
  108. if code != None:
  109. ret['Error'] = setErrorInfo(code, message)
  110. else:
  111. myLogger.info_logger("create job=" + app_id)
  112. # 根据请求创建新作业
  113. new_job = q.enqueue(install_app_delay, app_name, customer_app_name, app_version, job_id=app_id, timeout=3600)
  114. ret = ret.dict()
  115. return ret
  116. def start_app(app_id):
  117. ret = Response(code=const.RETURN_FAIL, message="")
  118. if docker.check_app_id(app_id):
  119. app_name = split_app_id(app_id)
  120. info, code = if_app_exits(app_id)
  121. if code:
  122. app_path = info.split()[-1].rsplit('/', 1)[0]
  123. docker.check_app_compose(app_path + '/.env')
  124. cmd = "docker compose -f " + app_path + "/docker-compose.yml start"
  125. output = shell_execute.execute_command_output_all(cmd)
  126. if int(output["code"]) == 0:
  127. ret.code = const.RETURN_SUCCESS
  128. ret.message = "The app starts successfully."
  129. else:
  130. ret.message = "The app failed to start!"
  131. else:
  132. ret.message = "This app is not currently installed."
  133. else:
  134. ret.message = "AppID is not legal!"
  135. ret = ret.dict()
  136. return ret
  137. def stop_app(app_id):
  138. ret = Response(code=const.RETURN_FAIL, message="")
  139. if docker.check_app_id(app_id):
  140. app_name = split_app_id(app_id)
  141. info, code = if_app_exits(app_id)
  142. if code:
  143. app_path = info.split()[-1].rsplit('/', 1)[0]
  144. cmd = "docker compose -f " + app_path + "/docker-compose.yml stop"
  145. output = shell_execute.execute_command_output_all(cmd)
  146. if int(output["code"]) == 0:
  147. ret.code = const.RETURN_SUCCESS
  148. ret.message = "The app stopped successfully."
  149. else:
  150. ret.message = "App stop failed!"
  151. else:
  152. ret.message = "This app is not currently installed."
  153. else:
  154. ret.message = 'AppID is not legal!'
  155. ret = ret.dict()
  156. return ret
  157. def restart_app(app_id):
  158. ret = Response(code=const.RETURN_FAIL, message="")
  159. if docker.check_app_id(app_id):
  160. app_name = split_app_id(app_id)
  161. info, code = if_app_exits(app_id)
  162. if code:
  163. app_path = info.split()[-1].rsplit('/', 1)[0]
  164. cmd = "docker compose -f " + app_path + "/docker-compose.yml restart"
  165. output = shell_execute.execute_command_output_all(cmd)
  166. if int(output["code"]) == 0:
  167. ret.code = const.RETURN_SUCCESS
  168. ret.message = "The app restarts successfully."
  169. else:
  170. ret.message = "App restart failed!"
  171. else:
  172. ret.message = "This app is not currently installed."
  173. else:
  174. ret.message = 'AppID is not legal!'
  175. ret = ret.dict()
  176. return ret
  177. def uninstall_app(app_id, delete_image, delete_data):
  178. ret = Response(code=const.RETURN_FAIL, message="")
  179. if docker.check_app_id(app_id):
  180. app_name = split_app_id(app_id)
  181. if_stopped = stop_app(app_id) # stop_app
  182. if if_stopped["code"] == 0:
  183. info, code = if_app_exits(app_id)
  184. app_path = info.split()[-1].rsplit('/', 1)[0]
  185. cmd = "docker compose -f " + app_path + "/docker-compose.yml down -v"
  186. lib_path = '/data/library/apps/' + app_name
  187. if app_path != lib_path:
  188. cmd = cmd + " && sudo rm -rf " + app_path
  189. output = shell_execute.execute_command_output_all(cmd)
  190. if int(output["code"]) == 0:
  191. ret.code = 0
  192. ret.message = "The app is deleted successfully"
  193. else:
  194. ret.message = "App deletion failed!"
  195. else:
  196. ret.message = if_stopped["message"]
  197. else:
  198. ret.message = 'AppID is not legal!'
  199. ret = ret.dict()
  200. return ret
  201. def check_app(app_name, customer_app_name, app_version):
  202. message = " "
  203. code = None
  204. app_id = app_name + "-" + customer_app_name
  205. if not docker.check_app_websoft9(app_name):
  206. code = 'Param.AppName.NotExis'
  207. message = "不支持安装指定的App"
  208. elif re.match('^[a-z0-9]+$', customer_app_name) == None:
  209. code = 'Param.CustomerAppName.FormatError'
  210. message = "用户自定义APP名称只能是数字和小写字母组成"
  211. elif docker.check_directory("/data/apps/" + customer_app_name):
  212. code = 'Param.CustomerAppName.Repeat'
  213. message = "已经安装了此应用,请重新指定APP名称"
  214. elif not docker.check_vm_resource(app_name):
  215. code = 'Requirement.NotEnough'
  216. message = "系统资源(cpu,内存,磁盘空间)不足"
  217. elif check_app_wait(app_id):
  218. code = 'Param.CustomerAppName.Wait'
  219. message = "同名应用已经在安装等待中,请重新指定APP名称"
  220. return code, message
  221. def prepare_app(app_name, customer_app_name):
  222. library_path = "/data/library/apps/" + app_name
  223. install_path = "/data/apps/" + customer_app_name
  224. message = " "
  225. code = const.RETURN_SUCCESS
  226. output = shell_execute.execute_command_output_all("cp -r " + library_path + " " + install_path)
  227. if int(output["code"]) != 0:
  228. message = "creating" + customer_app_name + "directory failed!"
  229. code = const.RETURN_FAIL
  230. return code, message
  231. def install_app_delay(app_name, customer_app_name, app_version):
  232. try:
  233. code, message = check_app(app_name, customer_app_name, app_version)
  234. if code == const.RETURN_SUCCESS:
  235. code, message = prepare_app(app_name, customer_app_name)
  236. if code == const.RETURN_SUCCESS:
  237. myLogger.info_logger("start job=" + customer_app_name)
  238. # modify env
  239. env_path = "/data/apps/" + customer_app_name + "/.env"
  240. docker.modify_env(env_path, 'APP_NAME', customer_app_name)
  241. docker.modify_env(env_path, "APP_VERSION", app_version)
  242. # check port
  243. docker.check_app_compose(env_path)
  244. cmd = "cd /data/apps/" + customer_app_name + " && sudo docker compose pull && sudo docker compose up -d"
  245. output = shell_execute.execute_command_output_all(cmd)
  246. myLogger.info_logger("install output")
  247. myLogger.info_logger(output["code"])
  248. myLogger.info_logger(output["result"])
  249. if int(output["code"]) != 0 or "error" in output["result"] or "fail" in output["result"]:
  250. raise Exception("installfailed!")
  251. else:
  252. return "success"
  253. else:
  254. raise Exception("prepare_app failed")
  255. else:
  256. raise Exception("resource check failed")
  257. except Exception as e:
  258. myLogger.info_logger(customer_app_name + "install failed!")
  259. myLogger.error_logger(e)
  260. job_id = app_name + "_" + customer_app_name
  261. try:
  262. uninstall_app(job_id)
  263. except Exception as e:
  264. myLogger.error_logger(e)
  265. raise Exception(e)
  266. def if_app_exits(app_id):
  267. app_name = app_id.split('_')[1]
  268. real_name = app_id.split('_')[0]
  269. flag = False
  270. info = ""
  271. cmd = "docker compose ls -a | grep \'/" + app_name + "/\'"
  272. output = shell_execute.execute_command_output_all(cmd)
  273. if int(output["code"]) == 0:
  274. info = output["result"]
  275. app_path = info.split()[-1].rsplit('/', 1)[0]
  276. is_official = check_if_official_app(app_path + '/variables.json')
  277. if is_official:
  278. name = docker.read_var(app_path + '/variables.json', 'name')
  279. if name == real_name:
  280. flag = True
  281. elif real_name == app_name:
  282. flag = True
  283. myLogger.info_logger("APP info: " + info)
  284. return info, flag
  285. def split_app_id(app_id):
  286. return app_id.split("_")[1]
  287. def get_apps_from_compose(output_list):
  288. ip_result = shell_execute.execute_command_output_all("curl ifconfig.me")
  289. ip = ip_result["result"]
  290. app_list = []
  291. has_add = []
  292. for app_info in output_list:
  293. volume = app_info["ConfigFiles"] # volume
  294. app_path = volume.rsplit('/', 1)[0]
  295. app_name = volume.split('/')[-2]
  296. app_id = app_name + "_" + app_name # app_id
  297. real_name = ""
  298. trade_mark = ""
  299. port = 0
  300. url = ""
  301. admin_url = ""
  302. image_url = ""
  303. user_name = ""
  304. password = ""
  305. official_app = False
  306. if app_name in ['appmanage', 'nginxproxymanager',
  307. 'redis'] and app_path == '/data/apps/stackhub/docker/' + app_name:
  308. continue
  309. # get code
  310. case = app_info["Status"].split("(")[0] # case
  311. if case == "running":
  312. case_code = const.APP_RUNNING # case_code
  313. elif case == "exited":
  314. case = "stop"
  315. case_code = const.APP_STOP
  316. elif case == "created":
  317. case_code = const.APP_READY
  318. case = "installing"
  319. else:
  320. case_code = const.APP_ERROR
  321. var_path = app_path + "/variables.json"
  322. official_app = check_if_official_app(var_path)
  323. if official_app:
  324. real_name = docker.read_var(var_path, 'name')
  325. app_id = real_name + "_" + app_name # app_id
  326. # get trade_mark
  327. trade_mark = docker.read_var(var_path, 'trademark')
  328. image_url = get_Image_url(real_name)
  329. # get env info
  330. path = app_path + "/.env"
  331. # get port and url
  332. try:
  333. http_port = list(docker.read_env(
  334. path, "APP_HTTP_PORT").values())[0]
  335. port = int(http_port)
  336. easy_url = "http://" + ip + ":" + str(port)
  337. url = get_url(real_name, easy_url)
  338. admin_url = get_admin_url(real_name, url)
  339. except IndexError:
  340. try:
  341. db_port = list(docker.read_env(
  342. path, "APP_DB.*_PORT").values())[0]
  343. port = int(db_port)
  344. except IndexError:
  345. pass
  346. # get user_name
  347. try:
  348. user_name = list(docker.read_env(path, "APP_USER").values())[0]
  349. except IndexError:
  350. pass
  351. # get password
  352. try:
  353. password = list(docker.read_env(
  354. path, "POWER_PASSWORD").values())[0]
  355. except IndexError:
  356. pass
  357. has_add.append(app_name)
  358. app = App(app_id=app_id, name=real_name, customer_name=app_name, status_code=case_code, status=case, port=port,
  359. volume=volume, url=url,
  360. image_url=image_url, admin_url=admin_url, trade_mark=trade_mark, user_name=user_name,
  361. password=password, official_app=official_app)
  362. app_list.append(app.dict())
  363. return app_list, has_add
  364. def check_if_official_app(var_path):
  365. if docker.check_directory(var_path):
  366. if docker.read_var(var_path, 'name') != "" and docker.read_var(var_path, 'trademark') != "" and docker.read_var(
  367. var_path, 'requirements') != "":
  368. requirements = docker.read_var(var_path, 'requirements')
  369. try:
  370. cpu = requirements['cpu']
  371. mem = requirements['memory']
  372. return True
  373. except:
  374. return False
  375. else:
  376. return False
  377. def check_app_wait(app_id):
  378. myLogger.info_logger("check_app_wait")
  379. deferred = DeferredJobRegistry(queue=q)
  380. wait_job_ids = deferred.get_job_ids()
  381. if app_id in wait_job_ids:
  382. return True
  383. else:
  384. return False
  385. def get_apps_from_queue():
  386. myLogger.info_logger("get queque apps...")
  387. # 获取 StartedJobRegistry 实例
  388. registry = StartedJobRegistry(queue=q)
  389. finish = FinishedJobRegistry(queue=q)
  390. deferred = DeferredJobRegistry(queue=q)
  391. failed = FailedJobRegistry(queue=q)
  392. scheduled = ScheduledJobRegistry(queue=q)
  393. # 获取正在执行的作业 ID 列表
  394. run_job_ids = registry.get_job_ids()
  395. finish_job_ids = finish.get_job_ids()
  396. wait_job_ids = deferred.get_job_ids()
  397. failed_jobs = failed.get_job_ids()
  398. scheduled_jobs = scheduled.get_job_ids()
  399. myLogger.info_logger(q.jobs)
  400. myLogger.info_logger(wait_job_ids)
  401. myLogger.info_logger(run_job_ids)
  402. myLogger.info_logger(finish_job_ids)
  403. myLogger.info_logger(failed_jobs)
  404. myLogger.info_logger(scheduled_jobs)
  405. myLogger.info_logger("----------------------------------------")
  406. installing_list = []
  407. for job in run_job_ids:
  408. app = get_installing_app(job, const.APP_READY, 'installing')
  409. installing_list.append(app)
  410. for job in q.jobs:
  411. app = get_installing_app(job.id, const.APP_WAIT, 'waiting')
  412. installing_list.append(app)
  413. for job_id in finish_job_ids:
  414. job = q.fetch_job(job_id)
  415. if job.result == "fail":
  416. app = get_installing_app(job_id, const.APP_ERROR, 'error')
  417. installing_list.append(app)
  418. return installing_list
  419. def get_installing_app(id, status_code, status):
  420. real_name = id.split('_')[0]
  421. app_name = id.split('_')[1]
  422. var_path = "/data/apps/" + app_name + "/variables.json"
  423. trade_mark = docker.read_var(var_path, 'trademark')
  424. real_name = docker.read_var(var_path, 'name')
  425. image_url = get_Image_url(real_name)
  426. app = App(app_id=real_name + "_" + app_name, name=real_name, customer_name=app_name,
  427. status_code=status_code, status=status, port=0, volume="",
  428. url="", image_url=image_url, admin_url="", trade_mark=trade_mark, user_name="",
  429. password="", official_app=True)
  430. return app
  431. def get_Image_url(app_name):
  432. image_url = "static/images/" + app_name + "-websoft9.png"
  433. return image_url
  434. def get_url(app_name, easy_url):
  435. url = easy_url
  436. if app_name == "joomla":
  437. url = easy_url + "/administrator"
  438. elif app_name == "other":
  439. url = easy_url + "/administrator"
  440. else:
  441. url = easy_url
  442. return url
  443. def get_admin_url(app_name, url):
  444. admin_url = ""
  445. if app_name == "wordpress":
  446. admin_url = url + "/wp-admin"
  447. elif app_name == "other":
  448. admin_url = url + "/admin"
  449. else:
  450. admin_url = ""
  451. return admin_url
  452. def setErrorInfo(code, message):
  453. error = {}
  454. error['code'] = code
  455. error['message'] = message
  456. return error