manage.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359
  1. from ast import Constant
  2. import os
  3. import io
  4. import sys
  5. import platform
  6. import shutil
  7. import time
  8. import subprocess
  9. import requests
  10. import json
  11. import datetime
  12. import socket
  13. import re
  14. from threading import Thread
  15. from api.utils import shell_execute, docker, const
  16. from api.model.app import App
  17. from api.service import db
  18. from api.model.response import Response
  19. from api.model.config import Config
  20. from api.model.status_reason import StatusReason
  21. from api.utils.log import myLogger
  22. from redis import Redis
  23. from rq import Queue, Worker, Connection
  24. from rq.registry import StartedJobRegistry, FinishedJobRegistry, DeferredJobRegistry, FailedJobRegistry, ScheduledJobRegistry, CanceledJobRegistry
  25. from api.exception.command_exception import CommandException
  26. from apscheduler.schedulers.background import BackgroundScheduler
  27. from apscheduler.triggers.date import DateTrigger
  28. from apscheduler.triggers.cron import CronTrigger
  29. from apscheduler.triggers.interval import IntervalTrigger
  30. from apscheduler.events import EVENT_SCHEDULER_PAUSED, EVENT_SCHEDULER_RESUMED,EVENT_SCHEDULER_STARTED,EVENT_SCHEDULER_SHUTDOWN
  31. # 指定 Redis 容器的主机名和端口
  32. redis_conn = Redis(host='websoft9-redis', port=6379)
  33. # 使用指定的 Redis 连接创建 RQ 队列
  34. q = Queue(connection=redis_conn, default_timeout=3600)
  35. def get_release_url():
  36. preview = db.AppSearchPreview().get("preview")
  37. myLogger.info_logger(preview)
  38. if preview == "false":
  39. return const.ARTIFACT_URL
  40. else:
  41. return const.ARTIFACT_URL_DEV
  42. def appstore_update():
  43. myLogger.info_logger("appstore update start...")
  44. # 当点击appstore升级时,是无条件升级,不需要做版本的判定
  45. release_url = get_release_url()
  46. download_url = release_url + "/plugin/appstore/appstore-latest.zip"
  47. cmd = "cd /opt && rm -rf /opt/appstore* && wget -q " + download_url + " && unzip -q appstore-latest.zip "
  48. shell_execute.execute_command_output_all(cmd)
  49. shell_execute.execute_command_output_all("rm -rf /usr/share/cockpit/appstore && cp -r /opt/appstore /usr/share/cockpit")
  50. shell_execute.execute_command_output_all("rm -rf /opt/appstore*")
  51. library_url = release_url + "/plugin/library/library-latest.zip"
  52. library_cmd = "cd /opt && rm -rf /opt/library* && wget -q " + library_url + " && unzip -q library-latest.zip "
  53. shell_execute.execute_command_output_all(library_cmd)
  54. shell_execute.execute_command_output_all("rm -rf /data/library && cp -r /opt/library /data")
  55. shell_execute.execute_command_output_all("rm -rf /opt/library*")
  56. myLogger.info_logger("auto update success...")
  57. # scheduler = BackgroundScheduler()
  58. # scheduler.add_job(add_hostname, 'interval', minutes=1)
  59. # scheduler.start()
  60. def AppPreviewUpdate(preview):
  61. myLogger.info_logger("AppPreviewUpdate")
  62. if preview == "true" or preview == "True":
  63. db.AppUpdatePreview(preview)
  64. return "true"
  65. elif preview == "false" or preview == "False":
  66. db.AppUpdatePreview(preview)
  67. return "false"
  68. elif preview == None or preview == "" or preview == "undefine":
  69. return db.AppSearchPreview().get("preview")
  70. else:
  71. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "preview is true,false,blank", "preview is true,false,blank")
  72. def AppAutoUpdate(auto_update):
  73. myLogger.info_logger("AppAutoUpdate")
  74. # myLogger.info_logger(scheduler.state)
  75. # myLogger.info_logger(scheduler.get_jobs())
  76. # if auto_update == "true" or auto_update == "True":
  77. # scheduler.resume()
  78. # return "true"
  79. # elif auto_update == "false" or auto_update == "False":
  80. # scheduler.pause()
  81. # return "false"
  82. # elif auto_update == None or auto_update == "" or auto_update == "undefine":
  83. # state = scheduler.state
  84. # if state == 1:
  85. # return "true"
  86. # else:
  87. # return "false"
  88. def AppStoreCore():
  89. release_url = get_release_url()
  90. version_cmd = "wget -O appstore.json " + release_url + "/plugin/appstore/appstore.json && cat appstore.json"
  91. latest = shell_execute.execute_command_output_all(version_cmd)['result']
  92. most_version = json.loads(latest)['Requires at most']
  93. least_version = json.loads(latest)['Requires at least']
  94. now = shell_execute.execute_command_output_all("cat /data/apps/websoft9/version.json")['result']
  95. now_version = json.loads(now)['VERSION']
  96. version_str = "now_version:" + now_version + " least_version:" + least_version + " most_version:" + most_version
  97. myLogger.info_logger(version_str)
  98. if now_version >= least_version and now_version <= most_version:
  99. return "0"
  100. elif now_version < least_version:
  101. return "-1"
  102. elif now_version > most_version:
  103. return "1"
  104. return "0"
  105. # 更新软件商店
  106. def AppStoreUpdate():
  107. core_support = AppStoreCore()
  108. release_url = get_release_url()
  109. if core_support == "-1":
  110. raise CommandException(const.ERRORMESSAGE_SERVER_VERSION_NEEDUPGRADE, "You must upgrade websoft9 core", "You must upgrade websoft9 core")
  111. elif core_support == "1":
  112. raise CommandException(const.ERRORMESSAGE_SERVER_VERSION_NOTSUPPORT, "core not support,can not upgrade", "core not support,can not upgrade")
  113. local_path = '/usr/share/cockpit/appstore/appstore.json'
  114. local_version = "0"
  115. try:
  116. op = shell_execute.execute_command_output_all("cat " + local_path)['result']
  117. local_version = json.loads(op)['Version']
  118. except:
  119. local_version = "0.0.0"
  120. version_cmd = "wget -O appstore.json " + release_url + "/plugin/appstore/appstore.json && cat appstore.json"
  121. latest = shell_execute.execute_command_output_all(version_cmd)['result']
  122. version = json.loads(latest)['Version']
  123. if local_version < version:
  124. appstore_update()
  125. else:
  126. myLogger.info_logger("You click update appstore, but not need to update")
  127. # 获取 update info
  128. def get_update_list():
  129. local_path = '/data/apps/websoft9/version.json'
  130. local_version = "0"
  131. try:
  132. op = shell_execute.execute_command_output_all("cat " + local_path)['result']
  133. local_version = json.loads(op)['VERSION']
  134. except:
  135. local_version = "0.0.0"
  136. version_cmd = "wget -O version.json " + const.ARTIFACT_URL + "/version.json && cat version.json"
  137. latest = shell_execute.execute_command_output_all(version_cmd)['result']
  138. version = json.loads(latest)['VERSION']
  139. ret = {}
  140. ret['local_version'] = local_version
  141. ret['target_version'] = version
  142. content = []
  143. date = ""
  144. if compared_version(local_version, version) == -1:
  145. ret['update'] = True
  146. cmd = "wget -O CHANGELOG.md " + const.ARTIFACT_URL + "/CHANGELOG.md && head -n 20 CHANGELOG.md "
  147. change_log_contents = shell_execute.execute_command_output_all(cmd)['result']
  148. change_log = change_log_contents.split('## ')[1].split('\n')
  149. date = change_log[0].split()[-1]
  150. for change in change_log[1:]:
  151. if change != '':
  152. content.append(change)
  153. else:
  154. ret['update'] = False
  155. ret['date'] = date
  156. ret['content'] = content
  157. return ret
  158. # 获取 appstore update info
  159. def get_appstore_update_list():
  160. release_url = get_release_url()
  161. local_path = '/usr/share/cockpit/appstore/appstore.json'
  162. local_version = "0"
  163. try:
  164. op = shell_execute.execute_command_output_all("cat " + local_path)['result']
  165. local_version = json.loads(op)['Version']
  166. except:
  167. local_version = "0.0.0"
  168. version_cmd = "wget -O appstore.json -N " + release_url + "/plugin/appstore/appstore.json && cat appstore.json"
  169. latest = shell_execute.execute_command_output_all(version_cmd)['result']
  170. version = json.loads(latest)['Version']
  171. ret = {}
  172. ret['local_version'] = local_version
  173. ret['target_version'] = version
  174. content = []
  175. date = ""
  176. core_compare = ""
  177. if compared_version(local_version, version) == -1:
  178. ret['update'] = True
  179. cmd = "wget -O CHANGELOG.md " + release_url + "/plugin/appstore/CHANGELOG.md && cat CHANGELOG.md"
  180. change_log_contents = shell_execute.execute_command_output_all(cmd)['result']
  181. change_log = change_log_contents.split('## ')[1].split('\n')
  182. date = change_log[0].split()[-1]
  183. for change in change_log[1:]:
  184. if change != '':
  185. content.append(change)
  186. core_compare = AppStoreCore()
  187. else:
  188. ret['update'] = False
  189. ret['date'] = date
  190. ret['content'] = content
  191. ret['core_compare'] = core_compare
  192. return ret
  193. def conbine_list(installing_list, installed_list):
  194. app_list = installing_list + installed_list
  195. result_list = []
  196. appid_list = []
  197. for app in app_list:
  198. app_id = app['app_id']
  199. if app_id in appid_list:
  200. continue
  201. else:
  202. appid_list.append(app_id)
  203. result_list.append(app)
  204. return result_list
  205. # 获取所有app的信息
  206. def get_my_app(app_id):
  207. installed_list = get_apps_from_compose()
  208. installing_list = get_apps_from_queue()
  209. app_list = conbine_list(installing_list, installed_list)
  210. find = False
  211. ret = {}
  212. if app_id != None:
  213. for app in app_list:
  214. if app_id == app['app_id']:
  215. ret = app
  216. find = True
  217. break
  218. if not find:
  219. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "This App doesn't exist!", "")
  220. else:
  221. ret = app_list
  222. myLogger.info_logger("app list result ok")
  223. return ret
  224. # 获取具体某个app的信息
  225. def get_app_status(app_id):
  226. code, message = docker.check_app_id(app_id)
  227. if code == None:
  228. app = get_my_app(app_id)
  229. # 将app_list 过滤出app_id的app,并缩减信息,使其符合文档的要求
  230. ret = {}
  231. ret['app_id'] = app['app_id']
  232. ret['status'] = app['status']
  233. ret['status_reason'] = app['status_reason']
  234. else:
  235. raise CommandException(code, message, '')
  236. return ret
  237. def install_app(app_name, customer_name, app_version):
  238. myLogger.info_logger("Install app ...")
  239. ret = {}
  240. ret['ResponseData'] = {}
  241. app_id = app_name + "_" + customer_name
  242. ret['ResponseData']['app_id'] = app_id
  243. code, message = check_app(app_name, customer_name, app_version)
  244. if code == None:
  245. q.enqueue(install_app_delay, app_name, customer_name, app_version, job_id=app_id)
  246. else:
  247. ret['Error'] = get_error_info(code, message, "")
  248. return ret
  249. def start_app(app_id):
  250. info, flag = app_exits_in_docker(app_id)
  251. if flag:
  252. app_path = info.split()[-1].rsplit('/', 1)[0]
  253. cmd = "docker compose -f " + app_path + "/docker-compose.yml start"
  254. shell_execute.execute_command_output_all(cmd)
  255. else:
  256. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  257. def stop_app(app_id):
  258. info, flag = app_exits_in_docker(app_id)
  259. if flag:
  260. app_path = info.split()[-1].rsplit('/', 1)[0]
  261. cmd = "docker compose -f " + app_path + "/docker-compose.yml stop"
  262. shell_execute.execute_command_output_all(cmd)
  263. else:
  264. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  265. def restart_app(app_id):
  266. code, message = docker.check_app_id(app_id)
  267. if code == None:
  268. info, flag = app_exits_in_docker(app_id)
  269. if flag:
  270. app_path = info.split()[-1].rsplit('/', 1)[0]
  271. cmd = "docker compose -f " + app_path + "/docker-compose.yml restart"
  272. shell_execute.execute_command_output_all(cmd)
  273. else:
  274. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  275. else:
  276. raise CommandException(code, message, "")
  277. def delete_app_failedjob(job_id):
  278. myLogger.info_logger("delete_app_failedjob")
  279. failed = FailedJobRegistry(queue=q)
  280. failed.remove(job_id, delete_job=True)
  281. def delete_app(app_id):
  282. try:
  283. app_name = app_id.split('_')[0]
  284. customer_name = app_id.split('_')[1]
  285. app_path = ""
  286. info, code_exist = app_exits_in_docker(app_id)
  287. if code_exist:
  288. app_path = info.split()[-1].rsplit('/', 1)[0]
  289. cmd = "docker compose -f " + app_path + "/docker-compose.yml down -v"
  290. lib_path = '/data/library/apps/' + app_name
  291. if app_path != lib_path:
  292. cmd = cmd + " && sudo rm -rf " + app_path
  293. try:
  294. myLogger.info_logger("Intall fail, down app and delete files")
  295. shell_execute.execute_command_output_all(cmd)
  296. except Exception:
  297. myLogger.info_logger("Delete app compose exception")
  298. # 强制删除失败又无法通过docker compose down 删除的容器
  299. try:
  300. myLogger.info_logger("IF delete fail, force to delete containers")
  301. force_cmd = "docker rm -f $(docker ps -f name=^" + customer_name + " -aq)"
  302. shell_execute.execute_command_output_all(force_cmd)
  303. except Exception:
  304. myLogger.info_logger("force delete app compose exception")
  305. else:
  306. if check_app_rq(app_id):
  307. delete_app_failedjob(app_id)
  308. else:
  309. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "AppID is not exist", "")
  310. cmd = " sudo rm -rf /data/apps/" + customer_name
  311. shell_execute.execute_command_output_all(cmd)
  312. except CommandException as ce:
  313. myLogger.info_logger("Delete app compose exception")
  314. def uninstall_app(app_id):
  315. app_name = app_id.split('_')[0]
  316. customer_name = app_id.split('_')[1]
  317. app_path = ""
  318. info, code_exist = app_exits_in_docker(app_id)
  319. if code_exist:
  320. app_path = info.split()[-1].rsplit('/', 1)[0]
  321. cmd = "docker compose -f " + app_path + "/docker-compose.yml down -v"
  322. lib_path = '/data/library/apps/' + app_name
  323. if app_path != lib_path:
  324. cmd = cmd + " && sudo rm -rf " + app_path
  325. shell_execute.execute_command_output_all(cmd)
  326. else:
  327. if check_app_rq(app_id):
  328. delete_app_failedjob(app_id)
  329. else:
  330. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "AppID is not exist", "")
  331. # Force to delete docker compose
  332. try:
  333. cmd = " sudo rm -rf /data/apps/" + customer_name
  334. shell_execute.execute_command_output_all(cmd)
  335. except CommandException as ce:
  336. myLogger.info_logger("Delete app compose exception")
  337. # Delete proxy config when uninstall app
  338. app_proxy_delete(app_id)
  339. def check_app(app_name, customer_name, app_version):
  340. message = ""
  341. code = None
  342. app_id = app_name + "_" + customer_name
  343. if app_name == None:
  344. code = const.ERROR_CLIENT_PARAM_BLANK
  345. message = "app_name is null"
  346. elif customer_name == None:
  347. code = const.ERROR_CLIENT_PARAM_BLANK
  348. message = "customer_name is null"
  349. elif len(customer_name) < 2:
  350. code = const.ERROR_CLIENT_PARAM_BLANK
  351. message = "customer_name must be longer than 2 chars"
  352. elif app_version == None:
  353. code = const.ERROR_CLIENT_PARAM_BLANK
  354. message = "app_version is null"
  355. elif app_version == "undefined" or app_version == "":
  356. code = const.ERROR_CLIENT_PARAM_BLANK
  357. message = "app_version is null"
  358. elif not docker.check_app_websoft9(app_name):
  359. code = const.ERROR_CLIENT_PARAM_NOTEXIST
  360. message = "It is not support to install " + app_name
  361. elif re.match('^[a-z0-9]+$', customer_name) == None:
  362. code = const.ERROR_CLIENT_PARAM_Format
  363. message = "APP name can only be composed of numbers and lowercase letters"
  364. elif docker.check_directory("/data/apps/" + customer_name):
  365. code = const.ERROR_CLIENT_PARAM_REPEAT
  366. message = "Repeat installation: " + customer_name
  367. elif not docker.check_vm_resource(app_name):
  368. code = const.ERROR_SERVER_RESOURCE
  369. message = "Insufficient system resources (cpu, memory, disk space)"
  370. elif check_app_docker(app_id):
  371. code = const.ERROR_CLIENT_PARAM_REPEAT
  372. message = "Repeat installation: " + customer_name
  373. elif check_app_rq(app_id):
  374. code = const.ERROR_CLIENT_PARAM_REPEAT
  375. message = "Repeat installation: " + customer_name
  376. return code, message
  377. def prepare_app(app_name, customer_name):
  378. library_path = "/data/library/apps/" + app_name
  379. install_path = "/data/apps/" + customer_name
  380. shell_execute.execute_command_output_all("cp -r " + library_path + " " + install_path)
  381. def install_app_delay(app_name, customer_name, app_version):
  382. myLogger.info_logger("-------RQ install start --------")
  383. job_id = app_name + "_" + customer_name
  384. try:
  385. # 因为这个时候还没有复制文件夹,是从/data/library里面文件读取json来检查的,应该是app_name,而不是customer_name
  386. resource_flag = docker.check_vm_resource(app_name)
  387. if resource_flag == True:
  388. myLogger.info_logger("job check ok, continue to install app")
  389. env_path = "/data/apps/" + customer_name + "/.env"
  390. # prepare_app(app_name, customer_name)
  391. docker.check_app_compose(app_name, customer_name)
  392. myLogger.info_logger("start JobID=" + job_id)
  393. docker.modify_env(env_path, 'APP_NAME', customer_name)
  394. docker.modify_env(env_path, "APP_VERSION", app_version)
  395. docker.check_app_url(customer_name)
  396. cmd = "cd /data/apps/" + customer_name + " && sudo docker compose pull && sudo docker compose up -d"
  397. output = shell_execute.execute_command_output_all(cmd)
  398. myLogger.info_logger("-------Install result--------")
  399. myLogger.info_logger(output["code"])
  400. myLogger.info_logger(output["result"])
  401. try:
  402. shell_execute.execute_command_output_all("bash /data/apps/" + customer_name + "/src/after_up.sh")
  403. except Exception as e:
  404. myLogger.info_logger(str(e))
  405. else:
  406. error_info = "##websoft9##" + const.ERROR_SERVER_RESOURCE + "##websoft9##" + "Insufficient system resources (cpu, memory, disk space)" + "##websoft9##" + "Insufficient system resources (cpu, memory, disk space)"
  407. myLogger.info_logger(error_info)
  408. raise Exception(error_info)
  409. except CommandException as ce:
  410. myLogger.info_logger(customer_name + " install failed(docker)!")
  411. delete_app(job_id)
  412. error_info = "##websoft9##" + ce.code + "##websoft9##" + ce.message + "##websoft9##" + ce.detail
  413. myLogger.info_logger(error_info)
  414. raise Exception(error_info)
  415. except Exception as e:
  416. myLogger.info_logger(customer_name + " install failed(system)!")
  417. delete_app(job_id)
  418. error_info = "##websoft9##" + const.ERROR_SERVER_SYSTEM + "##websoft9##" + 'system original error' + "##websoft9##" + str(
  419. e)
  420. myLogger.info_logger(error_info)
  421. raise Exception(error_info)
  422. def app_exits_in_docker(app_id):
  423. customer_name = app_id.split('_')[1]
  424. app_name = app_id.split('_')[0]
  425. flag = False
  426. info = ""
  427. cmd = "docker compose ls -a | grep \'/" + customer_name + "/\'"
  428. try:
  429. output = shell_execute.execute_command_output_all(cmd)
  430. if int(output["code"]) == 0:
  431. info = output["result"]
  432. app_path = info.split()[-1].rsplit('/', 1)[0]
  433. is_official = check_if_official_app(app_path + '/variables.json')
  434. if is_official:
  435. name = docker.read_var(app_path + '/variables.json', 'name')
  436. if name == app_name:
  437. flag = True
  438. elif app_name == customer_name:
  439. flag = True
  440. myLogger.info_logger("APP in docker")
  441. except CommandException as ce:
  442. myLogger.info_logger("APP not in docker")
  443. return info, flag
  444. def split_app_id(app_id):
  445. return app_id.split("_")[1]
  446. def get_createtime(official_app, app_path, customer_name):
  447. data_time = ""
  448. try:
  449. if official_app:
  450. cmd = "docker ps -f name=" + customer_name + " --format {{.RunningFor}} | head -n 1"
  451. result = shell_execute.execute_command_output_all(cmd)["result"].rstrip('\n')
  452. data_time = result
  453. else:
  454. cmd_all = "cd " + app_path + " && docker compose ps -a --format json"
  455. output = shell_execute.execute_command_output_all(cmd_all)
  456. container_name = json.loads(output["result"])[0]["Name"]
  457. cmd = "docker ps -f name=" + container_name + " --format {{.RunningFor}} | head -n 1"
  458. result = shell_execute.execute_command_output_all(cmd)["result"].rstrip('\n')
  459. data_time = result
  460. except Exception as e:
  461. myLogger.info_logger(str(e))
  462. myLogger.info_logger("get_createtime get success" + data_time)
  463. return data_time
  464. def get_apps_from_compose():
  465. myLogger.info_logger("Search all of apps ...")
  466. cmd = "docker compose ls -a --format json"
  467. output = shell_execute.execute_command_output_all(cmd)
  468. output_list = json.loads(output["result"])
  469. myLogger.info_logger(len(output_list))
  470. ip = "localhost"
  471. try:
  472. ip_result = shell_execute.execute_command_output_all("cat /data/apps/w9services/w9appmanage/public_ip")
  473. ip = ip_result["result"].rstrip('\n')
  474. except Exception:
  475. ip = "127.0.0.1"
  476. app_list = []
  477. for app_info in output_list:
  478. volume = app_info["ConfigFiles"]
  479. app_path = volume.rsplit('/', 1)[0]
  480. customer_name = volume.split('/')[-2]
  481. app_id = ""
  482. app_name = ""
  483. trade_mark = ""
  484. port = 0
  485. url = ""
  486. admin_url = ""
  487. image_url = ""
  488. user_name = ""
  489. password = ""
  490. official_app = False
  491. app_version = ""
  492. create_time = ""
  493. volume_data = ""
  494. config_path = app_path
  495. app_https = False
  496. app_replace_url = False
  497. default_domain = ""
  498. admin_path = ""
  499. admin_domain_url = ""
  500. if customer_name in ['w9appmanage', 'w9nginxproxymanager', 'w9redis', 'w9kopia',
  501. 'w9portainer'] or app_path == '/data/apps/w9services/' + customer_name:
  502. continue
  503. var_path = app_path + "/variables.json"
  504. official_app = check_if_official_app(var_path)
  505. status_show = app_info["Status"]
  506. status = app_info["Status"].split("(")[0]
  507. if status == "running" or status == "exited" or status == "restarting":
  508. if "exited" in status_show and "running" in status_show:
  509. if status == "exited":
  510. cmd = "docker ps -a -f name=" + customer_name + " --format {{.Names}}#{{.Status}}|grep Exited"
  511. result = shell_execute.execute_command_output_all(cmd)["result"].rstrip('\n')
  512. container = result.split("#Exited")[0]
  513. if container != customer_name:
  514. status = "running"
  515. if "restarting" in status_show:
  516. about_time = get_createtime(official_app, app_path, customer_name)
  517. if "seconds" in about_time:
  518. status = "restarting"
  519. else:
  520. status = "failed"
  521. elif status == "created":
  522. status = "failed"
  523. else:
  524. continue
  525. if official_app:
  526. app_name = docker.read_var(var_path, 'name')
  527. app_id = app_name + "_" + customer_name # app_id
  528. # get trade_mark
  529. trade_mark = docker.read_var(var_path, 'trademark')
  530. image_url = get_Image_url(app_name)
  531. # get env info
  532. path = app_path + "/.env"
  533. env_map = docker.get_map(path)
  534. try:
  535. myLogger.info_logger("get domain for APP_URL")
  536. domain = env_map.get("APP_URL")
  537. if "appname.example.com" in domain or ip in domain:
  538. default_domain = ""
  539. else:
  540. default_domain = domain
  541. except Exception:
  542. myLogger.info_logger("domain exception")
  543. try:
  544. app_version = env_map.get("APP_VERSION")
  545. volume_data = "/data/apps/" + customer_name + "/data"
  546. user_name = env_map.get("APP_USER", "")
  547. password = env_map.get("POWER_PASSWORD", "")
  548. admin_path = env_map.get("APP_ADMIN_PATH")
  549. if admin_path:
  550. myLogger.info_logger(admin_path)
  551. admin_path = admin_path.replace("\"", "")
  552. else:
  553. admin_path = ""
  554. if default_domain != "" and admin_path != "":
  555. admin_domain_url = "http://" + default_domain + admin_path
  556. except Exception:
  557. myLogger.info_logger("APP_USER POWER_PASSWORD exception")
  558. try:
  559. replace = env_map.get("APP_URL_REPLACE", "false")
  560. myLogger.info_logger("replace=" + replace)
  561. if replace == "true":
  562. app_replace_url = True
  563. https = env_map.get("APP_HTTPS_ACCESS", "false")
  564. if https == "true":
  565. app_https = True
  566. except Exception:
  567. myLogger.info_logger("APP_HTTPS_ACCESS exception")
  568. try:
  569. http_port = env_map.get("APP_HTTP_PORT", "0")
  570. if http_port:
  571. port = int(http_port)
  572. except Exception:
  573. pass
  574. if port != 0:
  575. try:
  576. if app_https:
  577. easy_url = "https://" + ip + ":" + str(port)
  578. else:
  579. easy_url = "http://" + ip + ":" + str(port)
  580. url = easy_url
  581. admin_url = get_admin_url(customer_name, url)
  582. except Exception:
  583. pass
  584. else:
  585. try:
  586. db_port = list(docker.read_env(path, "APP_DB.*_PORT").values())[0]
  587. port = int(db_port)
  588. except Exception:
  589. pass
  590. else:
  591. app_name = customer_name
  592. app_id = customer_name + "_" + customer_name
  593. create_time = get_createtime(official_app, app_path, customer_name)
  594. if status in ['running', 'exited']:
  595. config = Config(port=port, compose_file=volume, url=url, admin_url=admin_url,
  596. admin_domain_url=admin_domain_url,
  597. admin_path=admin_path, admin_username=user_name, admin_password=password,
  598. default_domain=default_domain)
  599. else:
  600. config = None
  601. if status == "failed":
  602. status_reason = StatusReason(Code=const.ERROR_SERVER_SYSTEM, Message="system original error",
  603. Detail="unknown error")
  604. else:
  605. status_reason = None
  606. app = App(app_id=app_id, app_name=app_name, customer_name=customer_name, trade_mark=trade_mark,
  607. app_version=app_version, create_time=create_time, volume_data=volume_data, config_path=config_path,
  608. status=status, status_reason=status_reason, official_app=official_app, image_url=image_url,
  609. app_https=app_https, app_replace_url=app_replace_url, config=config)
  610. app_list.append(app.dict())
  611. return app_list
  612. def check_if_official_app(var_path):
  613. if docker.check_directory(var_path):
  614. if docker.read_var(var_path, 'name') != "" and docker.read_var(var_path, 'trademark') != "" and docker.read_var(
  615. var_path, 'requirements') != "":
  616. requirements = docker.read_var(var_path, 'requirements')
  617. try:
  618. cpu = requirements['cpu']
  619. mem = requirements['memory']
  620. disk = requirements['disk']
  621. return True
  622. except KeyError:
  623. return False
  624. else:
  625. return False
  626. def check_app_docker(app_id):
  627. customer_name = app_id.split('_')[1]
  628. app_name = app_id.split('_')[0]
  629. flag = False
  630. cmd = "docker compose ls -a | grep \'/" + customer_name + "/\'"
  631. try:
  632. shell_execute.execute_command_output_all(cmd)
  633. flag = True
  634. myLogger.info_logger("APP in docker")
  635. except CommandException as ce:
  636. myLogger.info_logger("APP not in docker")
  637. return flag
  638. def check_app_rq(app_id):
  639. myLogger.info_logger("check_app_rq")
  640. started = StartedJobRegistry(queue=q)
  641. failed = FailedJobRegistry(queue=q)
  642. run_job_ids = started.get_job_ids()
  643. failed_job_ids = failed.get_job_ids()
  644. queue_job_ids = q.job_ids
  645. myLogger.info_logger(queue_job_ids)
  646. myLogger.info_logger(run_job_ids)
  647. myLogger.info_logger(failed_job_ids)
  648. if queue_job_ids and app_id in queue_job_ids:
  649. myLogger.info_logger("App in RQ")
  650. return True
  651. if failed_job_ids and app_id in failed_job_ids:
  652. myLogger.info_logger("App in RQ")
  653. return True
  654. if run_job_ids and app_id in run_job_ids:
  655. myLogger.info_logger("App in RQ")
  656. return True
  657. myLogger.info_logger("App not in RQ")
  658. return False
  659. def get_apps_from_queue():
  660. myLogger.info_logger("get queque apps...")
  661. # 获取 StartedJobRegistry 实例
  662. started = StartedJobRegistry(queue=q)
  663. finish = FinishedJobRegistry(queue=q)
  664. deferred = DeferredJobRegistry(queue=q)
  665. failed = FailedJobRegistry(queue=q)
  666. scheduled = ScheduledJobRegistry(queue=q)
  667. cancel = CanceledJobRegistry(queue=q)
  668. # 获取正在执行的作业 ID 列表
  669. run_job_ids = started.get_job_ids()
  670. finish_job_ids = finish.get_job_ids()
  671. wait_job_ids = deferred.get_job_ids()
  672. failed_jobs = failed.get_job_ids()
  673. scheduled_jobs = scheduled.get_job_ids()
  674. cancel_jobs = cancel.get_job_ids()
  675. myLogger.info_logger(q.jobs)
  676. myLogger.info_logger(run_job_ids)
  677. myLogger.info_logger(failed_jobs)
  678. myLogger.info_logger(cancel_jobs)
  679. myLogger.info_logger(wait_job_ids)
  680. myLogger.info_logger(finish_job_ids)
  681. myLogger.info_logger(scheduled_jobs)
  682. installing_list = []
  683. for job_id in run_job_ids:
  684. app = get_rq_app(job_id, 'installing', "", "", "")
  685. installing_list.append(app)
  686. for job in q.jobs:
  687. app = get_rq_app(job.id, 'installing', "", "", "")
  688. installing_list.append(app)
  689. for job_id in failed_jobs:
  690. job = q.fetch_job(job_id)
  691. exc_info = job.exc_info
  692. code = exc_info.split('##websoft9##')[1]
  693. message = exc_info.split('##websoft9##')[2]
  694. detail = exc_info.split('##websoft9##')[3]
  695. app = get_rq_app(job_id, 'failed', code, message, detail)
  696. installing_list.append(app)
  697. return installing_list
  698. def get_rq_app(id, status, code, message, detail):
  699. app_name = id.split('_')[0]
  700. customer_name = id.split('_')[1]
  701. # 当app还在RQ时,可能文件夹还没创建,无法获取trade_mark
  702. trade_mark = ""
  703. app_version = ""
  704. create_time = ""
  705. volume_data = ""
  706. config_path = ""
  707. image_url = get_Image_url(app_name)
  708. config = None
  709. if status == "installing":
  710. status_reason = None
  711. else:
  712. status_reason = StatusReason(Code=code, Message=message, Detail=detail)
  713. app = App(app_id=id, app_name=app_name, customer_name=customer_name, trade_mark=trade_mark,
  714. app_version=app_version, create_time=create_time, volume_data=volume_data, config_path=config_path,
  715. status=status, status_reason=status_reason, official_app=True, image_url=image_url,
  716. app_https=False, app_replace_url=False, config=config)
  717. return app.dict()
  718. def get_Image_url(app_name):
  719. image_url = "static/images/" + app_name + "-websoft9.png"
  720. return image_url
  721. def get_url(app_name, easy_url):
  722. url = easy_url
  723. return url
  724. def get_admin_url(customer_name, url):
  725. admin_url = ""
  726. path = "/data/apps/" + customer_name + "/.env"
  727. try:
  728. admin_path = list(docker.read_env(path, "APP_ADMIN_PATH").values())[0]
  729. admin_path = admin_path.replace("\"", "")
  730. admin_url = url + admin_path
  731. except IndexError:
  732. pass
  733. return admin_url
  734. def get_error_info(code, message, detail):
  735. error = {}
  736. error['Code'] = code
  737. error['Message'] = message
  738. error['Detail'] = detail
  739. return error
  740. def app_domain_list(app_id):
  741. code, message = docker.check_app_id(app_id)
  742. if code == None:
  743. info, flag = app_exits_in_docker(app_id)
  744. if flag:
  745. myLogger.info_logger("Check app_id ok[app_domain_list]")
  746. else:
  747. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  748. else:
  749. raise CommandException(code, message, "")
  750. domains = get_all_domains(app_id)
  751. myLogger.info_logger(domains)
  752. ret = {}
  753. ret['domains'] = domains
  754. default_domain = ""
  755. if domains != None and len(domains) > 0:
  756. customer_name = app_id.split('_')[1]
  757. app_url = shell_execute.execute_command_output_all("cat /data/apps/" + customer_name + "/.env")["result"]
  758. if "APP_URL" in app_url:
  759. url = shell_execute.execute_command_output_all("cat /data/apps/" + customer_name + "/.env |grep APP_URL=")[
  760. "result"].rstrip('\n')
  761. default_domain = url.split('=')[1]
  762. ret['default_domain'] = default_domain
  763. myLogger.info_logger(ret)
  764. return ret
  765. def app_proxy_delete(app_id):
  766. customer_name = app_id.split('_')[1]
  767. proxy_host = None
  768. token = get_token()
  769. url = const.NGINX_URL+"/api/nginx/proxy-hosts"
  770. headers = {
  771. 'Authorization': token,
  772. 'Content-Type': 'application/json'
  773. }
  774. response = requests.get(url, headers=headers)
  775. for proxy in response.json():
  776. portainer_name = proxy["forward_host"]
  777. if customer_name == portainer_name:
  778. proxy_id = proxy["id"]
  779. token = get_token()
  780. url = const.NGINX_URL+"/api/nginx/proxy-hosts/" + str(proxy_id)
  781. headers = {
  782. 'Authorization': token,
  783. 'Content-Type': 'application/json'
  784. }
  785. response = requests.delete(url, headers=headers)
  786. def app_domain_delete(app_id, domain):
  787. code, message = docker.check_app_id(app_id)
  788. if code == None:
  789. info, flag = app_exits_in_docker(app_id)
  790. if flag:
  791. myLogger.info_logger("Check app_id ok[app_domain_delete]")
  792. else:
  793. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  794. else:
  795. raise CommandException(code, message, "")
  796. if domain is None or domain == "undefined":
  797. raise CommandException(const.ERROR_CLIENT_PARAM_BLANK, "Domains is blank", "")
  798. old_all_domains = get_all_domains(app_id)
  799. if domain not in old_all_domains:
  800. myLogger.info_logger("delete domain is not binded")
  801. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "Domain is not bind.", "")
  802. myLogger.info_logger("Start to delete " + domain)
  803. proxy = get_proxy_domain(app_id, domain)
  804. if proxy != None:
  805. myLogger.info_logger(proxy)
  806. myLogger.info_logger("before update")
  807. domains_old = proxy["domain_names"]
  808. myLogger.info_logger(domains_old)
  809. domains_old.remove(domain)
  810. myLogger.info_logger("after update")
  811. myLogger.info_logger(domains_old)
  812. if len(domains_old) == 0:
  813. proxy_id = proxy["id"]
  814. token = get_token()
  815. url = const.NGINX_URL+"/api/nginx/proxy-hosts/" + str(proxy_id)
  816. headers = {
  817. 'Authorization': token,
  818. 'Content-Type': 'application/json'
  819. }
  820. response = requests.delete(url, headers=headers)
  821. try:
  822. if response.json().get("error"):
  823. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  824. except Exception:
  825. myLogger.info_logger(response.json())
  826. set_domain("", app_id)
  827. else:
  828. proxy_id = proxy["id"]
  829. token = get_token()
  830. url = const.NGINX_URL+"/api/nginx/proxy-hosts/" + str(proxy_id)
  831. headers = {
  832. 'Authorization': token,
  833. 'Content-Type': 'application/json'
  834. }
  835. port = get_container_port(app_id.split('_')[1])
  836. host = app_id.split('_')[1]
  837. data = {
  838. "domain_names": domains_old,
  839. "forward_scheme": "http",
  840. "forward_host": host,
  841. "forward_port": port,
  842. "access_list_id": "0",
  843. "certificate_id": 0,
  844. "meta": {
  845. "letsencrypt_agree": False,
  846. "dns_challenge": False
  847. },
  848. "advanced_config": "",
  849. "locations": [],
  850. "block_exploits": False,
  851. "caching_enabled": False,
  852. "allow_websocket_upgrade": False,
  853. "http2_support": False,
  854. "hsts_enabled": False,
  855. "hsts_subdomains": False,
  856. "ssl_forced": False
  857. }
  858. response = requests.put(url, data=json.dumps(data), headers=headers)
  859. if response.json().get("error"):
  860. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  861. domain_set = app_domain_list(app_id)
  862. default_domain = domain_set['default_domain']
  863. # 如果被删除的域名是默认域名,删除后去剩下域名的第一个
  864. if default_domain == domain:
  865. set_domain(domains_old[0], app_id)
  866. else:
  867. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "Delete domain is not bind", "")
  868. def app_domain_update(app_id, domain_old, domain_new):
  869. myLogger.info_logger("app_domain_update")
  870. domain_list = []
  871. domain_list.append(domain_old)
  872. domain_list.append(domain_new)
  873. check_domains(domain_list)
  874. code, message = docker.check_app_id(app_id)
  875. if code == None:
  876. info, flag = app_exits_in_docker(app_id)
  877. if flag:
  878. myLogger.info_logger("Check app_id ok")
  879. else:
  880. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  881. else:
  882. raise CommandException(code, message, "")
  883. proxy = get_proxy_domain(app_id, domain_old)
  884. if proxy != None:
  885. domains_old = proxy["domain_names"]
  886. index = domains_old.index(domain_old)
  887. domains_old[index] = domain_new
  888. proxy_id = proxy["id"]
  889. token = get_token()
  890. url = const.NGINX_URL+"/api/nginx/proxy-hosts/" + str(proxy_id)
  891. headers = {
  892. 'Authorization': token,
  893. 'Content-Type': 'application/json'
  894. }
  895. port = get_container_port(app_id.split('_')[1])
  896. host = app_id.split('_')[1]
  897. data = {
  898. "domain_names": domains_old,
  899. "forward_scheme": "http",
  900. "forward_host": host,
  901. "forward_port": port,
  902. "access_list_id": "0",
  903. "certificate_id": 0,
  904. "meta": {
  905. "letsencrypt_agree": False,
  906. "dns_challenge": False
  907. },
  908. "advanced_config": "",
  909. "locations": [],
  910. "block_exploits": False,
  911. "caching_enabled": False,
  912. "allow_websocket_upgrade": False,
  913. "http2_support": False,
  914. "hsts_enabled": False,
  915. "hsts_subdomains": False,
  916. "ssl_forced": False
  917. }
  918. response = requests.put(url, data=json.dumps(data), headers=headers)
  919. if response.json().get("error"):
  920. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  921. domain_set = app_domain_list(app_id)
  922. default_domain = domain_set['default_domain']
  923. myLogger.info_logger("default_domain=" + default_domain + ",domain_old=" + domain_old)
  924. # 如果被修改的域名是默认域名,修改后也设置为默认域名
  925. if default_domain == domain_old:
  926. set_domain(domain_new, app_id)
  927. else:
  928. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "edit domain is not exist", "")
  929. def app_domain_add(app_id, domain):
  930. temp_domains = []
  931. temp_domains.append(domain)
  932. check_domains(temp_domains)
  933. code, message = docker.check_app_id(app_id)
  934. if code == None:
  935. info, flag = app_exits_in_docker(app_id)
  936. if flag:
  937. myLogger.info_logger("Check app_id ok")
  938. else:
  939. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  940. else:
  941. raise CommandException(code, message, "")
  942. old_domains = get_all_domains(app_id)
  943. if domain in old_domains:
  944. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "Domain is in use", "")
  945. proxy = get_proxy(app_id)
  946. if proxy != None:
  947. domains_old = proxy["domain_names"]
  948. domain_list = domains_old
  949. domain_list.append(domain)
  950. proxy_id = proxy["id"]
  951. token = get_token()
  952. url = const.NGINX_URL+"/api/nginx/proxy-hosts/" + str(proxy_id)
  953. headers = {
  954. 'Authorization': token,
  955. 'Content-Type': 'application/json'
  956. }
  957. port = get_container_port(app_id.split('_')[1])
  958. host = app_id.split('_')[1]
  959. data = {
  960. "domain_names": domain_list,
  961. "forward_scheme": "http",
  962. "forward_host": host,
  963. "forward_port": port,
  964. "access_list_id": "0",
  965. "certificate_id": 0,
  966. "meta": {
  967. "letsencrypt_agree": False,
  968. "dns_challenge": False
  969. },
  970. "advanced_config": "",
  971. "locations": [],
  972. "block_exploits": False,
  973. "caching_enabled": False,
  974. "allow_websocket_upgrade": False,
  975. "http2_support": False,
  976. "hsts_enabled": False,
  977. "hsts_subdomains": False,
  978. "ssl_forced": False
  979. }
  980. response = requests.put(url, data=json.dumps(data), headers=headers)
  981. if response.json().get("error"):
  982. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  983. else:
  984. # 追加
  985. token = get_token()
  986. url = const.NGINX_URL+"/api/nginx/proxy-hosts"
  987. headers = {
  988. 'Authorization': token,
  989. 'Content-Type': 'application/json'
  990. }
  991. port = get_container_port(app_id.split('_')[1])
  992. host = app_id.split('_')[1]
  993. data = {
  994. "domain_names": temp_domains,
  995. "forward_scheme": "http",
  996. "forward_host": host,
  997. "forward_port": port,
  998. "access_list_id": "0",
  999. "certificate_id": 0,
  1000. "meta": {
  1001. "letsencrypt_agree": False,
  1002. "dns_challenge": False
  1003. },
  1004. "advanced_config": "",
  1005. "locations": [],
  1006. "block_exploits": False,
  1007. "caching_enabled": False,
  1008. "allow_websocket_upgrade": False,
  1009. "http2_support": False,
  1010. "hsts_enabled": False,
  1011. "hsts_subdomains": False,
  1012. "ssl_forced": False
  1013. }
  1014. response = requests.post(url, data=json.dumps(data), headers=headers)
  1015. if response.json().get("error"):
  1016. raise CommandException(const.ERROR_CONFIG_NGINX, response.json().get("error").get("message"), "")
  1017. set_domain(domain, app_id)
  1018. return domain
  1019. def check_domains(domains):
  1020. myLogger.info_logger(domains)
  1021. if domains is None or len(domains) == 0:
  1022. raise CommandException(const.ERROR_CLIENT_PARAM_BLANK, "Domains is blank", "")
  1023. else:
  1024. for domain in domains:
  1025. if is_valid_domain(domain):
  1026. if check_real_domain(domain) == False:
  1027. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "Domain and server not match", "")
  1028. else:
  1029. raise CommandException(const.ERROR_CLIENT_PARAM_Format, "Domains format error", "")
  1030. def is_valid_domain(domain):
  1031. if domain.startswith("http"):
  1032. return False
  1033. return True
  1034. def check_real_domain(domain):
  1035. domain_real = True
  1036. try:
  1037. cmd = "ping -c 1 " + domain + " | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | uniq"
  1038. domain_ip = shell_execute.execute_command_output_all(cmd)["result"].rstrip('\n')
  1039. ip_result = shell_execute.execute_command_output_all("cat /data/apps/w9services/w9appmanage/public_ip")
  1040. ip_save = ip_result["result"].rstrip('\n')
  1041. if domain_ip == ip_save:
  1042. myLogger.info_logger("Domain check ok!")
  1043. else:
  1044. domain_real = False
  1045. except CommandException as ce:
  1046. domain_real = False
  1047. return domain_real
  1048. def get_token():
  1049. url = const.NGINX_URL+"/api/tokens"
  1050. headers = {'Content-type': 'application/json'}
  1051. password = db.AppSearchUsers("nginx").get("password")
  1052. myLogger.info_logger("token_password: " + password)
  1053. param = {
  1054. "identity": "help@websoft9.com",
  1055. "scope": "user",
  1056. "secret": password
  1057. }
  1058. response = requests.post(url, data=json.dumps(param), headers=headers)
  1059. nginx_token = response.json()
  1060. myLogger.info_logger(nginx_token)
  1061. token = "Bearer " + response.json()["token"]
  1062. return token
  1063. def get_proxy(app_id):
  1064. customer_name = app_id.split('_')[1]
  1065. proxy_host = None
  1066. token = get_token()
  1067. url = const.NGINX_URL+"/api/nginx/proxy-hosts"
  1068. headers = {
  1069. 'Authorization': token,
  1070. 'Content-Type': 'application/json'
  1071. }
  1072. response = requests.get(url, headers=headers)
  1073. for proxy in response.json():
  1074. portainer_name = proxy["forward_host"]
  1075. if customer_name == portainer_name:
  1076. proxy_host = proxy
  1077. break
  1078. return proxy_host
  1079. def get_proxy_domain(app_id, domain):
  1080. customer_name = app_id.split('_')[1]
  1081. proxy_host = None
  1082. token = get_token()
  1083. url = const.NGINX_URL+"/api/nginx/proxy-hosts"
  1084. headers = {
  1085. 'Authorization': token,
  1086. 'Content-Type': 'application/json'
  1087. }
  1088. response = requests.get(url, headers=headers)
  1089. myLogger.info_logger(response.json())
  1090. for proxy in response.json():
  1091. portainer_name = proxy["forward_host"]
  1092. domain_list = proxy["domain_names"]
  1093. if customer_name == portainer_name:
  1094. myLogger.info_logger("-------------------")
  1095. if domain in domain_list:
  1096. myLogger.info_logger("find the domain proxy")
  1097. proxy_host = proxy
  1098. break
  1099. return proxy_host
  1100. def get_all_domains(app_id):
  1101. customer_name = app_id.split('_')[1]
  1102. domains = []
  1103. token = get_token()
  1104. url = const.NGINX_URL+"/api/nginx/proxy-hosts"
  1105. headers = {
  1106. 'Authorization': token,
  1107. 'Content-Type': 'application/json'
  1108. }
  1109. response = requests.get(url, headers=headers)
  1110. for proxy in response.json():
  1111. portainer_name = proxy["forward_host"]
  1112. if customer_name == portainer_name:
  1113. for domain in proxy["domain_names"]:
  1114. domains.append(domain)
  1115. return domains
  1116. def app_domain_set(domain, app_id):
  1117. temp_domains = []
  1118. temp_domains.append(domain)
  1119. check_domains(temp_domains)
  1120. code, message = docker.check_app_id(app_id)
  1121. if code == None:
  1122. info, flag = app_exits_in_docker(app_id)
  1123. if flag:
  1124. myLogger.info_logger("Check app_id ok")
  1125. else:
  1126. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
  1127. else:
  1128. raise CommandException(code, message, "")
  1129. set_domain(domain, app_id)
  1130. def set_domain(domain, app_id):
  1131. myLogger.info_logger("set_domain start")
  1132. old_domains = get_all_domains(app_id)
  1133. if domain != "":
  1134. if domain not in old_domains:
  1135. message = domain + " is not in use"
  1136. raise CommandException(const.ERROR_CLIENT_PARAM_NOTEXIST, message, "")
  1137. customer_name = app_id.split('_')[1]
  1138. app_url = shell_execute.execute_command_output_all("cat /data/apps/" + customer_name + "/.env")["result"]
  1139. if "APP_URL" in app_url:
  1140. myLogger.info_logger("APP_URL is exist")
  1141. if domain == "":
  1142. ip_result = shell_execute.execute_command_output_all("cat /data/apps/w9services/w9appmanage/public_ip")
  1143. domain = ip_result["result"].rstrip('\n')
  1144. cmd = "sed -i 's/APP_URL=.*/APP_URL=" + domain + "/g' /data/apps/" + customer_name + "/.env"
  1145. shell_execute.execute_command_output_all(cmd)
  1146. if "APP_URL_REPLACE=true" in app_url:
  1147. myLogger.info_logger("need up")
  1148. shell_execute.execute_command_output_all("cd /data/apps/" + customer_name + " && docker compose up -d")
  1149. else:
  1150. cmd = "sed -i 's/APP_URL=.*/APP_URL=" + domain + "/g' /data/apps/" + customer_name + "/.env"
  1151. shell_execute.execute_command_output_all(cmd)
  1152. if "APP_URL_REPLACE=true" in app_url:
  1153. myLogger.info_logger("need up")
  1154. shell_execute.execute_command_output_all("cd /data/apps/" + customer_name + " && docker compose up -d")
  1155. else:
  1156. myLogger.info_logger("APP_URL is not exist")
  1157. if domain == "":
  1158. ip_result = shell_execute.execute_command_output_all("cat /data/apps/w9services/w9appmanage/public_ip")
  1159. domain = ip_result["result"].rstrip('\n')
  1160. cmd = "sed -i '/APP_NETWORK/a APP_URL=" + domain + "' /data/apps/" + customer_name + "/.env"
  1161. shell_execute.execute_command_output_all(cmd)
  1162. myLogger.info_logger("set_domain success")
  1163. def get_container_port(container_name):
  1164. port = "80"
  1165. cmd = "docker port " + container_name + " |grep ::"
  1166. result = shell_execute.execute_command_output_all(cmd)["result"]
  1167. myLogger.info_logger(result)
  1168. port = result.split('/')[0]
  1169. myLogger.info_logger(port)
  1170. return port
  1171. def compared_version(ver1, ver2):
  1172. list1 = str(ver1).split(".")
  1173. list2 = str(ver2).split(".")
  1174. # 循环次数为短的列表的len
  1175. for i in range(len(list1)) if len(list1) < len(list2) else range(len(list2)):
  1176. if int(list1[i]) == int(list2[i]):
  1177. pass
  1178. elif int(list1[i]) < int(list2[i]):
  1179. return -1
  1180. else:
  1181. return 1
  1182. # 循环结束,哪个列表长哪个版本号高
  1183. if len(list1) == len(list2):
  1184. return 0
  1185. elif len(list1) < len(list2):
  1186. return -1
  1187. else:
  1188. return 1