manage.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075
  1. import os
  2. import io
  3. import sys
  4. import platform
  5. import shutil
  6. import time
  7. import subprocess
  8. import requests
  9. import json
  10. import datetime
  11. import socket
  12. import re
  13. from threading import Thread
  14. from api.utils import shell_execute, docker, const
  15. from api.model.app import App
  16. from api.model.response import Response
  17. from api.model.config import Config
  18. from api.model.status_reason import StatusReason
  19. from api.utils.common_log import myLogger
  20. from redis import Redis
  21. from rq import Queue, Worker, Connection
  22. from rq.registry import StartedJobRegistry, FinishedJobRegistry, DeferredJobRegistry, FailedJobRegistry, 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,default_timeout=3600)
  28. def conbine_list(installing_list, installed_list):
  29. app_list = installing_list + installed_list
  30. result_list = []
  31. appid_list = []
  32. for app in app_list:
  33. app_id = app['app_id']
  34. if app_id in appid_list:
  35. continue
  36. else:
  37. appid_list.append(app_id)
  38. result_list.append(app)
  39. return result_list
  40. # 获取所有app的信息
  41. def get_my_app(app_id):
  42. installed_list = get_apps_from_compose()
  43. installing_list = get_apps_from_queue()
  44. app_list = conbine_list(installing_list, installed_list)
  45. find = False
  46. ret = {}
  47. if app_id != None:
  48. for app in app_list:
  49. if app_id == app['app_id']:
  50. ret = app
  51. find = True
  52. break
  53. if not find:
  54. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "This App doesn't exist!", "")
  55. else:
  56. ret = app_list
  57. myLogger.info_logger("app list result ok")
  58. return ret
  59. # 获取具体某个app的信息
  60. def get_app_status(app_id):
  61. code, message = docker.check_app_id(app_id)
  62. if code == None:
  63. app = get_my_app(app_id)
  64. # 将app_list 过滤出app_id的app,并缩减信息,使其符合文档的要求
  65. ret = {}
  66. ret['app_id'] = app['app_id']
  67. ret['status'] = app['status']
  68. ret['status_reason'] = app['status_reason']
  69. else:
  70. raise CommandException(code, message, '')
  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)
  81. else:
  82. ret['Error'] = get_error_info(code, message, "")
  83. return ret
  84. def start_app(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. def stop_app(app_id):
  93. info, flag = app_exits_in_docker(app_id)
  94. if flag:
  95. app_path = info.split()[-1].rsplit('/', 1)[0]
  96. cmd = "docker compose -f " + app_path + "/docker-compose.yml stop"
  97. shell_execute.execute_command_output_all(cmd)
  98. else:
  99. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  100. def restart_app(app_id):
  101. code, message = docker.check_app_id(app_id)
  102. if code == None:
  103. info, flag = app_exits_in_docker(app_id)
  104. if flag:
  105. app_path = info.split()[-1].rsplit('/', 1)[0]
  106. cmd = "docker compose -f " + app_path + "/docker-compose.yml restart"
  107. shell_execute.execute_command_output_all(cmd)
  108. else:
  109. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  110. else:
  111. raise CommandException(code, message, "")
  112. def delete_app_failedjob(job_id):
  113. myLogger.info_logger("delete_app_failedjob")
  114. failed = FailedJobRegistry(queue=q)
  115. failed.remove(job_id, delete_job=True)
  116. def uninstall_app(app_id):
  117. app_name = app_id.split('_')[0]
  118. customer_name = app_id.split('_')[1]
  119. app_path = ""
  120. info, code_exist = app_exits_in_docker(app_id)
  121. if code_exist:
  122. app_path = info.split()[-1].rsplit('/', 1)[0]
  123. cmd = "docker compose -f " + app_path + "/docker-compose.yml down -v"
  124. lib_path = '/data/library/apps/' + app_name
  125. if app_path != lib_path:
  126. cmd = cmd + " && sudo rm -rf " + app_path
  127. shell_execute.execute_command_output_all(cmd)
  128. else:
  129. if check_app_rq(app_id):
  130. delete_app_failedjob(app_id)
  131. else:
  132. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "AppID is not exist", "")
  133. # Force to delete docker compose
  134. try:
  135. cmd = " sudo rm -rf /data/apps/" + customer_name
  136. shell_execute.execute_command_output_all(cmd)
  137. except CommandException as ce:
  138. myLogger.info_logger("Delete app compose exception")
  139. # Delete proxy config when uninstall app
  140. app_proxy_delete(app_id)
  141. def check_app(app_name, customer_name, app_version):
  142. message = ""
  143. code = None
  144. app_id = app_name + "_" + customer_name
  145. if app_name == None:
  146. code = const.ERROR_CLIENT_PARAM_BLANK
  147. message = "app_name is null"
  148. elif customer_name == None:
  149. code = const.ERROR_CLIENT_PARAM_BLANK
  150. message = "customer_name is null"
  151. elif app_version == None:
  152. code = const.ERROR_CLIENT_PARAM_BLANK
  153. message = "app_version is null"
  154. elif app_version == "undefined" or app_version == "":
  155. code = const.ERROR_CLIENT_PARAM_BLANK
  156. message = "app_version is null"
  157. elif not docker.check_app_websoft9(app_name):
  158. code = const.ERROR_CLIENT_PARAM_NOTEXIST
  159. message = "It is not support to install " + app_name
  160. elif re.match('^[a-z0-9]+$', customer_name) == None:
  161. code = const.ERROR_CLIENT_PARAM_Format
  162. message = "APP name can only be composed of numbers and lowercase letters"
  163. elif docker.check_directory("/data/apps/" + customer_name):
  164. code = const.ERROR_CLIENT_PARAM_REPEAT
  165. message = "Repeat installation: " + customer_name
  166. elif not docker.check_vm_resource(app_name):
  167. code = const.ERROR_SERVER_RESOURCE
  168. message = "Insufficient system resources (cpu, memory, disk space)"
  169. elif check_app_docker(app_id):
  170. code = const.ERROR_CLIENT_PARAM_REPEAT
  171. message = "Repeat installation: " + customer_name
  172. elif check_app_rq(app_id):
  173. code = const.ERROR_CLIENT_PARAM_REPEAT
  174. message = "Repeat installation: " + customer_name
  175. return code, message
  176. def prepare_app(app_name, customer_name):
  177. library_path = "/data/library/apps/" + app_name
  178. install_path = "/data/apps/" + customer_name
  179. shell_execute.execute_command_output_all("cp -r " + library_path + " " + install_path)
  180. def install_app_delay(app_name, customer_name, app_version):
  181. myLogger.info_logger("-------RQ install start --------")
  182. job_id = app_name + "_" + customer_name
  183. try:
  184. # 因为这个时候还没有复制文件夹,是从/data/library里面文件读取json来检查的,应该是app_name,而不是customer_name
  185. resource_flag = docker.check_vm_resource(app_name)
  186. if resource_flag == True:
  187. myLogger.info_logger("job check ok, continue to install app")
  188. env_path = "/data/apps/" + customer_name + "/.env"
  189. # prepare_app(app_name, customer_name)
  190. docker.check_app_compose(app_name, customer_name)
  191. myLogger.info_logger("start JobID=" + job_id)
  192. docker.modify_env(env_path, 'APP_NAME', customer_name)
  193. docker.modify_env(env_path, "APP_VERSION", app_version)
  194. docker.check_app_url(customer_name)
  195. cmd = "cd /data/apps/" + customer_name + " && sudo docker compose pull && sudo docker compose up -d"
  196. output = shell_execute.execute_command_output_all(cmd)
  197. myLogger.info_logger("-------Install result--------")
  198. myLogger.info_logger(output["code"])
  199. myLogger.info_logger(output["result"])
  200. try:
  201. shell_execute.execute_command_output_all("bash /data/apps/" + customer_name + "/src/after_up.sh")
  202. except Exception as e:
  203. myLogger.info_logger(str(e))
  204. else:
  205. error_info= "##websoft9##" + const.ERROR_SERVER_RESOURCE + "##websoft9##" + "Insufficient system resources (cpu, memory, disk space)" + "##websoft9##" + "Insufficient system resources (cpu, memory, disk space)"
  206. myLogger.info_logger(error_info)
  207. raise Exception(error_info)
  208. except CommandException as ce:
  209. myLogger.info_logger(customer_name + " install failed(docker)!")
  210. uninstall_app(job_id)
  211. error_info= "##websoft9##" + ce.code + "##websoft9##" + ce.message + "##websoft9##" + ce.detail
  212. myLogger.info_logger(error_info)
  213. raise Exception(error_info)
  214. except Exception as e:
  215. myLogger.info_logger(customer_name + " install failed(system)!")
  216. uninstall_app(job_id)
  217. error_info= "##websoft9##" + const.ERROR_SERVER_SYSTEM + "##websoft9##" + 'system original error' + "##websoft9##" + str(e)
  218. myLogger.info_logger(error_info)
  219. raise Exception(error_info)
  220. def app_exits_in_docker(app_id):
  221. customer_name = app_id.split('_')[1]
  222. app_name = app_id.split('_')[0]
  223. flag = False
  224. info = ""
  225. cmd = "docker compose ls -a | grep \'/" + customer_name + "/\'"
  226. try:
  227. output = shell_execute.execute_command_output_all(cmd)
  228. if int(output["code"]) == 0:
  229. info = output["result"]
  230. app_path = info.split()[-1].rsplit('/', 1)[0]
  231. is_official = check_if_official_app(app_path + '/variables.json')
  232. if is_official:
  233. name = docker.read_var(app_path + '/variables.json', 'name')
  234. if name == app_name:
  235. flag = True
  236. elif app_name == customer_name:
  237. flag = True
  238. myLogger.info_logger("APP in docker")
  239. except CommandException as ce:
  240. myLogger.info_logger("APP not in docker")
  241. return info, flag
  242. def split_app_id(app_id):
  243. return app_id.split("_")[1]
  244. def get_createtime(official_app, app_path, customer_name):
  245. data_time = ""
  246. try:
  247. if official_app:
  248. cmd = "docker ps -f name=" + customer_name + " --format {{.RunningFor}} | head -n 1"
  249. result = shell_execute.execute_command_output_all(cmd)["result"].rstrip('\n')
  250. data_time = result
  251. else:
  252. cmd_all = "cd " + app_path + " && docker compose ps -a --format json"
  253. output = shell_execute.execute_command_output_all(cmd_all)
  254. container_name = json.loads(output["result"])[0]["Name"]
  255. cmd = "docker ps -f name=" + customer_name + " --format {{.RunningFor}} | head -n 1"
  256. result = shell_execute.execute_command_output_all(cmd)["result"].rstrip('\n')
  257. data_time = result
  258. except Exception as e:
  259. myLogger.info_logger(str(e))
  260. myLogger.info_logger("get_createtime get success"+data_time)
  261. return data_time
  262. def get_apps_from_compose():
  263. myLogger.info_logger("Search all of apps ...")
  264. cmd = "docker compose ls -a --format json"
  265. output = shell_execute.execute_command_output_all(cmd)
  266. output_list = json.loads(output["result"])
  267. myLogger.info_logger(len(output_list))
  268. ip = "localhost"
  269. try:
  270. ip_result = shell_execute.execute_command_output_all("cat /data/apps/stackhub/docker/w9appmanage/public_ip")
  271. ip = ip_result["result"].rstrip('\n')
  272. except Exception:
  273. ip = "127.0.0.1"
  274. app_list = []
  275. for app_info in output_list:
  276. volume = app_info["ConfigFiles"]
  277. app_path = volume.rsplit('/', 1)[0]
  278. customer_name = volume.split('/')[-2]
  279. app_id = ""
  280. app_name = ""
  281. trade_mark = ""
  282. port = 0
  283. url = ""
  284. admin_url = ""
  285. image_url = ""
  286. user_name = ""
  287. password = ""
  288. official_app = False
  289. app_version = ""
  290. create_time = ""
  291. volume_data = ""
  292. config_path = app_path
  293. app_https = False
  294. app_replace_url = False
  295. default_domain = ""
  296. admin_path = ""
  297. admin_domain_url = ""
  298. if customer_name in ['w9appmanage', 'w9nginxproxymanager','w9redis','w9portainer'] and app_path == '/data/apps/stackhub/docker/' + customer_name:
  299. continue
  300. status_show = app_info["Status"]
  301. status = app_info["Status"].split("(")[0]
  302. if status == "running" or status == "exited" or status == "restarting":
  303. if "exited" in status_show and "running" in status_show:
  304. if status == "exited":
  305. cmd = "docker ps -a -f name=" + customer_name + " --format {{.Names}}#{{.Status}}|grep Exited"
  306. result = shell_execute.execute_command_output_all(cmd)["result"].rstrip('\n')
  307. container = result.split("#Exited")[0]
  308. if container != customer_name:
  309. status = "running"
  310. elif status == "created":
  311. status = "failed"
  312. else:
  313. continue
  314. var_path = app_path + "/variables.json"
  315. official_app = check_if_official_app(var_path)
  316. if official_app:
  317. app_name = docker.read_var(var_path, 'name')
  318. app_id = app_name + "_" + customer_name # app_id
  319. # get trade_mark
  320. trade_mark = docker.read_var(var_path, 'trademark')
  321. image_url = get_Image_url(app_name)
  322. # get env info
  323. path = app_path + "/.env"
  324. env_map = docker.get_map(path)
  325. try:
  326. myLogger.info_logger("get domain for APP_URL")
  327. domain = env_map.get("APP_URL")
  328. if "appname.example.com" in domain or ip in domain:
  329. default_domain = ""
  330. else:
  331. default_domain = domain
  332. except Exception:
  333. myLogger.info_logger("domain exception")
  334. try:
  335. app_version = env_map.get("APP_VERSION")
  336. volume_data = "/data/apps/" + customer_name + "/data"
  337. user_name = env_map.get("APP_USER","")
  338. password = env_map.get("POWER_PASSWORD","")
  339. admin_path = env_map.get("APP_ADMIN_PATH")
  340. if admin_path:
  341. myLogger.info_logger(admin_path)
  342. admin_path = admin_path.replace("\"","")
  343. else:
  344. admin_path =""
  345. if default_domain != "" and admin_path != "":
  346. admin_domain_url = "http://" + default_domain + admin_path
  347. except Exception:
  348. myLogger.info_logger("APP_USER POWER_PASSWORD exception")
  349. try:
  350. replace = env_map.get("APP_URL_REPLACE","false")
  351. myLogger.info_logger("replace="+replace)
  352. if replace == "true":
  353. app_replace_url = True
  354. https = env_map.get("APP_HTTPS_ACCESS","false")
  355. if https == "true":
  356. app_https = True
  357. except Exception:
  358. myLogger.info_logger("APP_HTTPS_ACCESS exception")
  359. try:
  360. http_port = env_map.get("APP_HTTP_PORT","0")
  361. if http_port:
  362. port = int(http_port)
  363. except Exception:
  364. pass
  365. if port != 0:
  366. try:
  367. if app_https:
  368. easy_url = "https://" + ip + ":" + str(port)
  369. else:
  370. easy_url = "http://" + ip + ":" + str(port)
  371. url = easy_url
  372. admin_url = get_admin_url(customer_name, url)
  373. except Exception:
  374. pass
  375. else:
  376. try:
  377. db_port = list(docker.read_env(path, "APP_DB.*_PORT").values())[0]
  378. port = int(db_port)
  379. except Exception:
  380. pass
  381. else:
  382. app_name = customer_name
  383. app_id = customer_name + "_" + customer_name
  384. create_time = get_createtime(official_app, app_path, customer_name)
  385. if status in ['running', 'exited']:
  386. config = Config(port=port, compose_file=volume, url=url, admin_url=admin_url,admin_domain_url=admin_domain_url,
  387. admin_path=admin_path,admin_username=user_name, admin_password=password, default_domain=default_domain)
  388. else:
  389. config = None
  390. if status == "failed":
  391. status_reason = StatusReason(Code=const.ERROR_SERVER_SYSTEM, Message="system original error", Detail="unknown error")
  392. else:
  393. status_reason = None
  394. app = App(app_id=app_id, app_name=app_name, customer_name=customer_name, trade_mark=trade_mark,
  395. app_version=app_version,create_time=create_time,volume_data=volume_data,config_path=config_path,
  396. status=status, status_reason=status_reason, official_app=official_app, image_url=image_url,
  397. app_https=app_https,app_replace_url=app_replace_url,config=config)
  398. app_list.append(app.dict())
  399. return app_list
  400. def check_if_official_app(var_path):
  401. if docker.check_directory(var_path):
  402. if docker.read_var(var_path, 'name') != "" and docker.read_var(var_path, 'trademark') != "" and docker.read_var(
  403. var_path, 'requirements') != "":
  404. requirements = docker.read_var(var_path, 'requirements')
  405. try:
  406. cpu = requirements['cpu']
  407. mem = requirements['memory']
  408. disk = requirements['disk']
  409. return True
  410. except KeyError:
  411. return False
  412. else:
  413. return False
  414. def check_app_docker(app_id):
  415. customer_name = app_id.split('_')[1]
  416. app_name = app_id.split('_')[0]
  417. flag = False
  418. cmd = "docker compose ls -a | grep \'/" + customer_name + "/\'"
  419. try:
  420. shell_execute.execute_command_output_all(cmd)
  421. flag = True
  422. myLogger.info_logger("APP in docker")
  423. except CommandException as ce:
  424. myLogger.info_logger("APP not in docker")
  425. return flag
  426. def check_app_rq(app_id):
  427. myLogger.info_logger("check_app_rq")
  428. started = StartedJobRegistry(queue=q)
  429. failed = FailedJobRegistry(queue=q)
  430. run_job_ids = started.get_job_ids()
  431. failed_job_ids = failed.get_job_ids()
  432. queue_job_ids = q.job_ids
  433. myLogger.info_logger(queue_job_ids)
  434. myLogger.info_logger(run_job_ids)
  435. myLogger.info_logger(failed_job_ids)
  436. if queue_job_ids and app_id in queue_job_ids:
  437. myLogger.info_logger("App in RQ")
  438. return True
  439. if failed_job_ids and app_id in failed_job_ids:
  440. myLogger.info_logger("App in RQ")
  441. return True
  442. if run_job_ids and app_id in run_job_ids:
  443. myLogger.info_logger("App in RQ")
  444. return True
  445. myLogger.info_logger("App not in RQ")
  446. return False
  447. def get_apps_from_queue():
  448. myLogger.info_logger("get queque apps...")
  449. # 获取 StartedJobRegistry 实例
  450. started = StartedJobRegistry(queue=q)
  451. finish = FinishedJobRegistry(queue=q)
  452. deferred = DeferredJobRegistry(queue=q)
  453. failed = FailedJobRegistry(queue=q)
  454. scheduled = ScheduledJobRegistry(queue=q)
  455. cancel = CanceledJobRegistry(queue=q)
  456. # 获取正在执行的作业 ID 列表
  457. run_job_ids = started.get_job_ids()
  458. finish_job_ids = finish.get_job_ids()
  459. wait_job_ids = deferred.get_job_ids()
  460. failed_jobs = failed.get_job_ids()
  461. scheduled_jobs = scheduled.get_job_ids()
  462. cancel_jobs = cancel.get_job_ids()
  463. myLogger.info_logger(q.jobs)
  464. myLogger.info_logger(run_job_ids)
  465. myLogger.info_logger(failed_jobs)
  466. myLogger.info_logger(cancel_jobs)
  467. myLogger.info_logger(wait_job_ids)
  468. myLogger.info_logger(finish_job_ids)
  469. myLogger.info_logger(scheduled_jobs)
  470. installing_list = []
  471. for job_id in run_job_ids:
  472. app = get_rq_app(job_id, 'installing', "", "", "")
  473. installing_list.append(app)
  474. for job in q.jobs:
  475. app = get_rq_app(job.id, 'installing', "", "", "")
  476. installing_list.append(app)
  477. for job_id in failed_jobs:
  478. job = q.fetch_job(job_id)
  479. exc_info = job.exc_info
  480. code = exc_info.split('##websoft9##')[1]
  481. message = exc_info.split('##websoft9##')[2]
  482. detail = exc_info.split('##websoft9##')[3]
  483. app = get_rq_app(job_id, 'failed', code, message, detail)
  484. installing_list.append(app)
  485. return installing_list
  486. def get_rq_app(id, status, code, message, detail):
  487. app_name = id.split('_')[0]
  488. customer_name = id.split('_')[1]
  489. # 当app还在RQ时,可能文件夹还没创建,无法获取trade_mark
  490. trade_mark = ""
  491. app_version = ""
  492. create_time = ""
  493. volume_data = ""
  494. config_path = ""
  495. image_url = get_Image_url(app_name)
  496. config = None
  497. if status == "installing" :
  498. status_reason = None
  499. else:
  500. status_reason = StatusReason(Code=code, Message=message, Detail=detail)
  501. app = App(app_id=id, app_name=app_name, customer_name=customer_name, trade_mark=trade_mark,
  502. app_version=app_version,create_time=create_time,volume_data=volume_data,config_path=config_path,
  503. status=status, status_reason=status_reason, official_app=True, image_url=image_url,
  504. app_https=False,app_replace_url=False,config=config)
  505. return app.dict()
  506. def get_Image_url(app_name):
  507. image_url = "static/images/" + app_name + "-websoft9.png"
  508. return image_url
  509. def get_url(app_name, easy_url):
  510. url = easy_url
  511. return url
  512. def get_admin_url(customer_name, url):
  513. admin_url = ""
  514. path = "/data/apps/" + customer_name + "/.env"
  515. try:
  516. admin_path = list(docker.read_env(path, "APP_ADMIN_PATH").values())[0]
  517. admin_path = admin_path.replace("\"","")
  518. admin_url = url + admin_path
  519. except IndexError:
  520. pass
  521. return admin_url
  522. def get_error_info(code, message, detail):
  523. error = {}
  524. error['Code'] = code
  525. error['Message'] = message
  526. error['Detail'] = detail
  527. return error
  528. def app_domain_list(app_id):
  529. code, message = docker.check_app_id(app_id)
  530. if code == None:
  531. info, flag = app_exits_in_docker(app_id)
  532. if flag:
  533. myLogger.info_logger("Check app_id ok[app_domain_list]")
  534. else:
  535. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  536. else:
  537. raise CommandException(code, message, "")
  538. domains = get_all_domains(app_id)
  539. myLogger.info_logger(domains)
  540. ret = {}
  541. ret['domains'] = domains
  542. default_domain = ""
  543. if domains != None and len(domains) > 0:
  544. customer_name = app_id.split('_')[1]
  545. app_url = shell_execute.execute_command_output_all("cat /data/apps/" + customer_name +"/.env")["result"]
  546. if "APP_URL" in app_url:
  547. url = shell_execute.execute_command_output_all("cat /data/apps/" + customer_name +"/.env |grep APP_URL=")["result"].rstrip('\n')
  548. default_domain = url.split('=')[1]
  549. ret['default_domain'] = default_domain
  550. myLogger.info_logger(ret)
  551. return ret
  552. def app_proxy_delete(app_id):
  553. customer_name = app_id.split('_')[1]
  554. proxy_host = None
  555. token = get_token()
  556. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts"
  557. headers = {
  558. 'Authorization': token,
  559. 'Content-Type': 'application/json'
  560. }
  561. response = requests.get(url, headers=headers)
  562. for proxy in response.json():
  563. portainer_name = proxy["forward_host"]
  564. if customer_name == portainer_name:
  565. proxy_id = proxy["id"]
  566. token = get_token()
  567. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts/" + str(proxy_id)
  568. headers = {
  569. 'Authorization': token,
  570. 'Content-Type': 'application/json'
  571. }
  572. response = requests.delete(url, headers=headers)
  573. def app_domain_delete(app_id, domain):
  574. code, message = docker.check_app_id(app_id)
  575. if code == None:
  576. info, flag = app_exits_in_docker(app_id)
  577. if flag:
  578. myLogger.info_logger("Check app_id ok[app_domain_delete]")
  579. else:
  580. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  581. else:
  582. raise CommandException(code, message, "")
  583. if domain is None or domain == "undefined":
  584. raise CommandException(const.ERROR_CLIENT_PARAM_BLANK, "Domains is blank", "")
  585. old_all_domains = get_all_domains(app_id)
  586. if domain not in old_all_domains:
  587. myLogger.info_logger("delete domain is not binded")
  588. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "Domain is not bind.", "")
  589. myLogger.info_logger("Start to delete " + domain)
  590. proxy = get_proxy_domain(app_id, domain)
  591. if proxy != None:
  592. myLogger.info_logger(proxy)
  593. myLogger.info_logger("before update")
  594. domains_old = proxy["domain_names"]
  595. myLogger.info_logger(domains_old)
  596. domains_old.remove(domain)
  597. myLogger.info_logger("after update")
  598. myLogger.info_logger(domains_old)
  599. if len(domains_old) == 0:
  600. proxy_id = proxy["id"]
  601. token = get_token()
  602. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts/" + str(proxy_id)
  603. headers = {
  604. 'Authorization': token,
  605. 'Content-Type': 'application/json'
  606. }
  607. response = requests.delete(url, headers=headers)
  608. try:
  609. if response.json().get("error"):
  610. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  611. except Exception:
  612. myLogger.info_logger(response.json())
  613. set_domain("", app_id)
  614. else:
  615. proxy_id = proxy["id"]
  616. token = get_token()
  617. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts/" + str(proxy_id)
  618. headers = {
  619. 'Authorization': token,
  620. 'Content-Type': 'application/json'
  621. }
  622. port = get_container_port(app_id.split('_')[1])
  623. host = app_id.split('_')[1]
  624. data = {
  625. "domain_names": domains_old,
  626. "forward_scheme": "http",
  627. "forward_host": host,
  628. "forward_port": port,
  629. "access_list_id": "0",
  630. "certificate_id": 0,
  631. "meta": {
  632. "letsencrypt_agree": False,
  633. "dns_challenge": False
  634. },
  635. "advanced_config": "",
  636. "locations": [],
  637. "block_exploits": False,
  638. "caching_enabled": False,
  639. "allow_websocket_upgrade": False,
  640. "http2_support": False,
  641. "hsts_enabled": False,
  642. "hsts_subdomains": False,
  643. "ssl_forced": False
  644. }
  645. response = requests.put(url, data=json.dumps(data), headers=headers)
  646. if response.json().get("error"):
  647. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  648. domain_set = app_domain_list(app_id)
  649. default_domain = domain_set['default_domain']
  650. # 如果被删除的域名是默认域名,删除后去剩下域名的第一个
  651. if default_domain == domain:
  652. set_domain(domains_old[0], app_id)
  653. else:
  654. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "Delete domain is not bind", "")
  655. def app_domain_update(app_id, domain_old, domain_new):
  656. myLogger.info_logger("app_domain_update")
  657. domain_list = []
  658. domain_list.append(domain_old)
  659. domain_list.append(domain_new)
  660. check_domains(domain_list)
  661. code, message = docker.check_app_id(app_id)
  662. if code == None:
  663. info, flag = app_exits_in_docker(app_id)
  664. if flag:
  665. myLogger.info_logger("Check app_id ok")
  666. else:
  667. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  668. else:
  669. raise CommandException(code, message, "")
  670. proxy = get_proxy_domain(app_id, domain_old)
  671. if proxy != None:
  672. domains_old = proxy["domain_names"]
  673. index = domains_old.index(domain_old)
  674. domains_old[index] = domain_new
  675. proxy_id = proxy["id"]
  676. token = get_token()
  677. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts/" + str(proxy_id)
  678. headers = {
  679. 'Authorization': token,
  680. 'Content-Type': 'application/json'
  681. }
  682. port = get_container_port(app_id.split('_')[1])
  683. host = app_id.split('_')[1]
  684. data = {
  685. "domain_names": domains_old,
  686. "forward_scheme": "http",
  687. "forward_host": host,
  688. "forward_port": port,
  689. "access_list_id": "0",
  690. "certificate_id": 0,
  691. "meta": {
  692. "letsencrypt_agree": False,
  693. "dns_challenge": False
  694. },
  695. "advanced_config": "",
  696. "locations": [],
  697. "block_exploits": False,
  698. "caching_enabled": False,
  699. "allow_websocket_upgrade": False,
  700. "http2_support": False,
  701. "hsts_enabled": False,
  702. "hsts_subdomains": False,
  703. "ssl_forced": False
  704. }
  705. response = requests.put(url, data=json.dumps(data), headers=headers)
  706. if response.json().get("error"):
  707. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  708. domain_set = app_domain_list(app_id)
  709. default_domain = domain_set['default_domain']
  710. myLogger.info_logger("default_domain=" + default_domain + ",domain_old="+domain_old)
  711. # 如果被修改的域名是默认域名,修改后也设置为默认域名
  712. if default_domain == domain_old:
  713. set_domain(domain_new, app_id)
  714. else:
  715. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "edit domain is not exist", "")
  716. def app_domain_add(app_id, domain):
  717. temp_domains = []
  718. temp_domains.append(domain)
  719. check_domains(temp_domains)
  720. code, message = docker.check_app_id(app_id)
  721. if code == None:
  722. info, flag = app_exits_in_docker(app_id)
  723. if flag:
  724. myLogger.info_logger("Check app_id ok")
  725. else:
  726. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  727. else:
  728. raise CommandException(code, message, "")
  729. old_domains = get_all_domains(app_id)
  730. if domain in old_domains:
  731. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "Domain is in use", "")
  732. proxy = get_proxy(app_id)
  733. if proxy != None:
  734. domains_old = proxy["domain_names"]
  735. domain_list = domains_old
  736. domain_list.append(domain)
  737. proxy_id = proxy["id"]
  738. token = get_token()
  739. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts/" + str(proxy_id)
  740. headers = {
  741. 'Authorization': token,
  742. 'Content-Type': 'application/json'
  743. }
  744. port = get_container_port(app_id.split('_')[1])
  745. host = app_id.split('_')[1]
  746. data = {
  747. "domain_names": domain_list,
  748. "forward_scheme": "http",
  749. "forward_host": host,
  750. "forward_port": port,
  751. "access_list_id": "0",
  752. "certificate_id": 0,
  753. "meta": {
  754. "letsencrypt_agree": False,
  755. "dns_challenge": False
  756. },
  757. "advanced_config": "",
  758. "locations": [],
  759. "block_exploits": False,
  760. "caching_enabled": False,
  761. "allow_websocket_upgrade": False,
  762. "http2_support": False,
  763. "hsts_enabled": False,
  764. "hsts_subdomains": False,
  765. "ssl_forced": False
  766. }
  767. response = requests.put(url, data=json.dumps(data), headers=headers)
  768. if response.json().get("error"):
  769. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  770. else:
  771. # 追加
  772. token = get_token()
  773. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts"
  774. headers = {
  775. 'Authorization': token,
  776. 'Content-Type': 'application/json'
  777. }
  778. port = get_container_port(app_id.split('_')[1])
  779. host = app_id.split('_')[1]
  780. data = {
  781. "domain_names": temp_domains,
  782. "forward_scheme": "http",
  783. "forward_host": host,
  784. "forward_port": port,
  785. "access_list_id": "0",
  786. "certificate_id": 0,
  787. "meta": {
  788. "letsencrypt_agree": False,
  789. "dns_challenge": False
  790. },
  791. "advanced_config": "",
  792. "locations": [],
  793. "block_exploits": False,
  794. "caching_enabled": False,
  795. "allow_websocket_upgrade": False,
  796. "http2_support": False,
  797. "hsts_enabled": False,
  798. "hsts_subdomains": False,
  799. "ssl_forced": False
  800. }
  801. response = requests.post(url, data=json.dumps(data), headers=headers)
  802. if response.json().get("error"):
  803. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  804. set_domain(domain, app_id)
  805. return domain
  806. def check_domains(domains):
  807. myLogger.info_logger(domains)
  808. if domains is None or len(domains) == 0:
  809. raise CommandException(const.ERROR_CLIENT_PARAM_BLANK, "Domains is blank", "")
  810. else:
  811. for domain in domains:
  812. if is_valid_domain(domain):
  813. if check_real_domain(domain) == False:
  814. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "Domain and server not match", "")
  815. else:
  816. raise CommandException(const.ERROR_CLIENT_PARAM_Format, "Domains format error", "")
  817. def is_valid_domain(domain):
  818. if domain.startswith("http"):
  819. return False
  820. return True
  821. def check_real_domain(domain):
  822. domain_real = True
  823. try:
  824. cmd = "ping -c 1 " + domain + " | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | uniq"
  825. domain_ip = shell_execute.execute_command_output_all(cmd)["result"].rstrip('\n')
  826. ip_result = shell_execute.execute_command_output_all("cat /data/apps/stackhub/docker/w9appmanage/public_ip")
  827. ip_save = ip_result["result"].rstrip('\n')
  828. if domain_ip == ip_save:
  829. myLogger.info_logger("Domain check ok!")
  830. else:
  831. domain_real = False
  832. except CommandException as ce:
  833. domain_real = False
  834. return domain_real
  835. def get_token():
  836. url = 'http://172.17.0.1:9092/api/tokens'
  837. headers = {'Content-type': 'application/json'}
  838. cmd = "cat /usr/share/cockpit/nginx/config.json | jq -r '.NGINXPROXYMANAGER_PASSWORD'"
  839. password = shell_execute.execute_command_output_all(cmd)["result"].rstrip('\n')
  840. myLogger.info_logger(password)
  841. param = {
  842. "identity": "help@websoft9.com",
  843. "scope": "user",
  844. "secret": password
  845. }
  846. response = requests.post(url, data=json.dumps(param), headers=headers)
  847. token = "Bearer " + response.json()["token"]
  848. return token
  849. def get_proxy(app_id):
  850. customer_name = app_id.split('_')[1]
  851. proxy_host = None
  852. token = get_token()
  853. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts"
  854. headers = {
  855. 'Authorization': token,
  856. 'Content-Type': 'application/json'
  857. }
  858. response = requests.get(url, headers=headers)
  859. for proxy in response.json():
  860. portainer_name = proxy["forward_host"]
  861. if customer_name == portainer_name:
  862. proxy_host = proxy
  863. break
  864. return proxy_host
  865. def get_proxy_domain(app_id, domain):
  866. customer_name = app_id.split('_')[1]
  867. proxy_host = None
  868. token = get_token()
  869. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts"
  870. headers = {
  871. 'Authorization': token,
  872. 'Content-Type': 'application/json'
  873. }
  874. response = requests.get(url, headers=headers)
  875. myLogger.info_logger(response.json())
  876. for proxy in response.json():
  877. portainer_name = proxy["forward_host"]
  878. domain_list = proxy["domain_names"]
  879. if customer_name == portainer_name:
  880. myLogger.info_logger("-------------------")
  881. if domain in domain_list:
  882. myLogger.info_logger("find the domain proxy")
  883. proxy_host = proxy
  884. break
  885. return proxy_host
  886. def get_all_domains(app_id):
  887. customer_name = app_id.split('_')[1]
  888. domains = []
  889. token = get_token()
  890. url = "http://172.17.0.1:9092/api/nginx/proxy-hosts"
  891. headers = {
  892. 'Authorization': token,
  893. 'Content-Type': 'application/json'
  894. }
  895. response = requests.get(url, headers=headers)
  896. for proxy in response.json():
  897. portainer_name = proxy["forward_host"]
  898. if customer_name == portainer_name:
  899. for domain in proxy["domain_names"]:
  900. domains.append(domain)
  901. return domains
  902. def app_domain_set(domain, app_id):
  903. temp_domains = []
  904. temp_domains.append(domain)
  905. check_domains(temp_domains)
  906. code, message = docker.check_app_id(app_id)
  907. if code == None:
  908. info, flag = app_exits_in_docker(app_id)
  909. if flag:
  910. myLogger.info_logger("Check app_id ok")
  911. else:
  912. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  913. else:
  914. raise CommandException(code, message, "")
  915. set_domain(domain, app_id)
  916. def set_domain(domain, app_id):
  917. myLogger.info_logger("set_domain start")
  918. old_domains = get_all_domains(app_id)
  919. if domain != "":
  920. if domain not in old_domains:
  921. message = domain + " is not in use"
  922. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, message, "")
  923. customer_name = app_id.split('_')[1]
  924. app_url = shell_execute.execute_command_output_all("cat /data/apps/" + customer_name +"/.env")["result"]
  925. if "APP_URL" in app_url:
  926. myLogger.info_logger("APP_URL is exist")
  927. if domain == "":
  928. ip_result = shell_execute.execute_command_output_all("cat /data/apps/stackhub/docker/w9appmanage/public_ip")
  929. domain = ip_result["result"].rstrip('\n')
  930. cmd = "sed -i 's/APP_URL=.*/APP_URL=" + domain + "/g' /data/apps/" + customer_name +"/.env"
  931. shell_execute.execute_command_output_all(cmd)
  932. if "APP_URL_REPLACE=true" in app_url:
  933. myLogger.info_logger("need up")
  934. shell_execute.execute_command_output_all("cd /data/apps/" + customer_name + " && docker compose up -d")
  935. else:
  936. cmd = "sed -i 's/APP_URL=.*/APP_URL=" + domain + "/g' /data/apps/" + customer_name +"/.env"
  937. shell_execute.execute_command_output_all(cmd)
  938. if "APP_URL_REPLACE=true" in app_url:
  939. myLogger.info_logger("need up")
  940. shell_execute.execute_command_output_all("cd /data/apps/" + customer_name + " && docker compose up -d")
  941. else:
  942. myLogger.info_logger("APP_URL is not exist")
  943. if domain == "":
  944. ip_result = shell_execute.execute_command_output_all("cat /data/apps/stackhub/docker/w9appmanage/public_ip")
  945. domain = ip_result["result"].rstrip('\n')
  946. cmd = "sed -i '/APP_NETWORK/a APP_URL=" + domain + "' /data/apps/" + customer_name +"/.env"
  947. shell_execute.execute_command_output_all(cmd)
  948. myLogger.info_logger("set_domain success")
  949. def get_container_port(container_name):
  950. port = "80"
  951. cmd = "docker port "+ container_name + " |grep ::"
  952. result = shell_execute.execute_command_output_all(cmd)["result"]
  953. myLogger.info_logger(result)
  954. port = result.split('/')[0]
  955. myLogger.info_logger(port)
  956. return port