websoft9/appmanage/api/service/manage.py

459 lines
16 KiB
Python
Raw Normal View History

2023-03-15 08:59:06 +08:00
import os
import io
import sys
import platform
import shutil
import time
import subprocess
import json
import datetime
import socket
2023-03-21 15:14:35 +08:00
import re
from threading import Thread
2023-03-24 15:35:58 +08:00
from api.utils import shell_execute, docker, const
from api.model.app import App
2023-03-07 16:55:23 +08:00
from api.model.response import Response
2023-04-14 14:36:37 +08:00
from api.model.running_info import RunningInfo
from api.model.status_reason import StatusReason
2023-03-28 17:16:07 +08:00
from api.utils.common_log import myLogger
2023-04-04 10:22:05 +08:00
from redis import Redis
2023-04-04 17:25:23 +08:00
from rq import Queue, Worker, Connection
2023-04-14 19:42:42 +08:00
from rq.registry import StartedJobRegistry, FinishedJobRegistry, DeferredJobRegistry, FailedJobRegistry, \
ScheduledJobRegistry, CanceledJobRegistry
2023-04-14 16:41:43 +08:00
from api.exception.command_exception import CommandException
2023-04-04 10:22:05 +08:00
# 指定 Redis 容器的主机名和端口
2023-04-04 15:28:21 +08:00
redis_conn = Redis(host='websoft9-redis', port=6379)
2023-04-04 10:22:05 +08:00
# 使用指定的 Redis 连接创建 RQ 队列
q = Queue(connection=redis_conn)
2023-04-15 08:50:09 +08:00
def AppList():
myLogger.info_logger("Install app ...")
ret = {}
ret['ResponseData'] = {}
app_id = app_name + "_" + customer_name
ret['ResponseData']= get_my_app()
2023-04-14 19:42:42 +08:00
# 获取所有app的信息
def get_my_app():
2023-04-15 07:43:22 +08:00
# get all info
cmd = "docker compose ls -a --format json"
output = shell_execute.execute_command_output_all(cmd)
output_list = json.loads(output["result"])
installed_list, has_add = get_apps_from_compose(output_list)
installing_list = get_apps_from_queue()
app_list = installed_list + installing_list
return app_list
2023-03-25 20:31:44 +08:00
2023-03-24 15:23:39 +08:00
# 获取具体某个app的信息
2023-04-15 07:43:22 +08:00
def get_app_status(app_id):
2023-04-15 08:50:09 +08:00
myLogger.info_logger("Install app ...")
ret = {}
ret['ResponseData'] = {}
2023-04-14 19:42:42 +08:00
code, message = docker.check_app_id(app_id)
2023-04-15 08:50:09 +08:00
2023-04-14 19:42:42 +08:00
if code == None:
2023-04-15 07:43:22 +08:00
app_list = get_my_app()
# 将app_list 过滤出app_id的app并缩减信息使其符合文档的要求
2023-03-27 16:26:03 +08:00
else:
2023-04-15 08:50:09 +08:00
ret['Error'] = get_error_info(code, message, "")
2023-04-14 19:42:42 +08:00
2023-04-14 12:04:07 +08:00
def install_app(app_name, customer_name, app_version):
2023-04-04 10:22:05 +08:00
myLogger.info_logger("Install app ...")
2023-04-12 17:11:53 +08:00
ret = {}
ret['ResponseData'] = {}
2023-04-14 12:04:07 +08:00
app_id = app_name + "_" + customer_name
ret['ResponseData']['app_id'] = app_id
2023-04-14 13:15:35 +08:00
code, message = check_app(app_name, customer_name, app_version)
if code == None:
2023-04-14 19:42:42 +08:00
q.enqueue(install_app_delay, app_name, customer_name, app_version, job_id=app_id, timeout=3600)
2023-04-14 13:15:35 +08:00
else:
2023-04-14 19:42:42 +08:00
ret['Error'] = get_error_info(code, message, "")
2023-04-14 12:04:07 +08:00
2023-03-21 15:14:35 +08:00
return ret
2023-02-28 17:44:04 +08:00
2023-03-15 16:13:17 +08:00
def start_app(app_id):
2023-04-15 08:35:33 +08:00
ret = {}
ret['ResponseData'] = {}
ret['ResponseData']['app_id'] = app_id
code, message = docker.check_app_id(app_id)
if code == None:
2023-03-27 16:26:03 +08:00
app_name = split_app_id(app_id)
2023-04-15 08:35:33 +08:00
info, flag = app_exits_in_docker(app_id)
if flag:
2023-03-28 16:07:39 +08:00
app_path = info.split()[-1].rsplit('/', 1)[0]
cmd = "docker compose -f " + app_path + "/docker-compose.yml start"
2023-04-15 08:35:33 +08:00
shell_execute.execute_command_output_all(cmd)
2023-02-28 17:44:04 +08:00
else:
2023-04-15 08:35:33 +08:00
ret['Error'] = get_error_info(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
2023-02-28 17:44:04 +08:00
else:
2023-04-15 08:35:33 +08:00
ret['Error'] = get_error_info(code, message, "")
2023-02-28 17:44:04 +08:00
2023-04-15 08:35:33 +08:00
return ret
2023-04-07 09:52:22 +08:00
2023-03-15 16:13:17 +08:00
def stop_app(app_id):
2023-04-15 08:35:33 +08:00
ret = {}
ret['ResponseData'] = {}
ret['ResponseData']['app_id'] = app_id
code, message = docker.check_app_id(app_id)
if code == None:
2023-03-27 16:26:03 +08:00
app_name = split_app_id(app_id)
2023-04-15 08:35:33 +08:00
info, flag = app_exits_in_docker(app_id)
if flag:
2023-03-28 16:07:39 +08:00
app_path = info.split()[-1].rsplit('/', 1)[0]
cmd = "docker compose -f " + app_path + "/docker-compose.yml stop"
2023-04-15 08:35:33 +08:00
shell_execute.execute_command_output_all(cmd)
2023-02-28 17:44:04 +08:00
else:
2023-04-15 08:35:33 +08:00
ret['Error'] = get_error_info(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
2023-02-28 17:44:04 +08:00
else:
2023-04-15 08:35:33 +08:00
ret['Error'] = get_error_info(code, message, "")
2023-02-28 17:44:04 +08:00
2023-04-15 08:35:33 +08:00
return ret
2023-04-07 09:52:22 +08:00
2023-03-15 16:13:17 +08:00
def restart_app(app_id):
2023-04-15 08:35:33 +08:00
ret = {}
ret['ResponseData'] = {}
ret['ResponseData']['app_id'] = app_id
code, message = docker.check_app_id(app_id)
if code == None:
2023-03-27 16:26:03 +08:00
app_name = split_app_id(app_id)
2023-04-15 08:35:33 +08:00
info, flag = app_exits_in_docker(app_id)
if flag:
2023-03-28 16:07:39 +08:00
app_path = info.split()[-1].rsplit('/', 1)[0]
cmd = "docker compose -f " + app_path + "/docker-compose.yml restart"
2023-04-15 08:35:33 +08:00
shell_execute.execute_command_output_all(cmd)
2023-02-28 17:44:04 +08:00
else:
2023-04-15 08:35:33 +08:00
ret['Error'] = get_error_info(const.ERROR_CLIENT_PARAM_NOTEXIST, "APP is not exist", "")
2023-02-28 17:44:04 +08:00
else:
2023-04-15 08:35:33 +08:00
ret['Error'] = get_error_info(code, message, "")
2023-02-28 17:44:04 +08:00
2023-04-15 08:35:33 +08:00
return ret
2023-03-15 08:59:06 +08:00
2023-04-14 18:38:30 +08:00
def delete_app_failedjob(app_id):
2023-04-14 19:42:42 +08:00
myLogger.info_logger("delete_app_failedjob")
2023-04-14 18:38:30 +08:00
def uninstall_app(app_id):
ret = {}
ret['ResponseData'] = {}
ret['ResponseData']['app_id'] = app_id
2023-04-15 08:35:33 +08:00
code, message = docker.check_appid_include_rq(app_id)
2023-04-14 18:38:30 +08:00
if code == None:
2023-03-30 14:31:18 +08:00
app_name = split_app_id(app_id)
2023-04-15 08:04:46 +08:00
info, code_exist = app_exits_in_docker(app_id)
2023-04-14 18:38:30 +08:00
if code_exist:
2023-03-31 18:34:04 +08:00
app_path = info.split()[-1].rsplit('/', 1)[0]
2023-03-31 18:26:50 +08:00
cmd = "docker compose -f " + app_path + "/docker-compose.yml down -v"
lib_path = '/data/library/apps/' + app_name
if app_path != lib_path:
cmd = cmd + " && sudo rm -rf " + app_path
2023-04-14 18:38:30 +08:00
shell_execute.execute_command_output_all(cmd)
2023-02-28 17:44:04 +08:00
else:
2023-04-14 18:38:30 +08:00
delete_app_failedjob(app_id)
2023-03-31 18:26:50 +08:00
else:
2023-04-14 19:42:42 +08:00
ret['Error'] = get_error_info(code, message, "")
2023-02-28 17:44:04 +08:00
return ret
2023-03-24 15:54:08 +08:00
2023-04-14 13:15:35 +08:00
def check_app(app_name, customer_name, app_version):
2023-04-14 16:41:43 +08:00
message = ""
2023-04-12 17:11:53 +08:00
code = None
2023-04-14 13:15:35 +08:00
app_id = app_name + "-" + customer_name
if app_name == None:
code = const.ERROR_CLIENT_PARAM_BLANK
message = "app_name is null"
elif customer_name == None:
code = const.ERROR_CLIENT_PARAM_BLANK
message = "customer_name is null"
elif app_version == None:
code = const.ERROR_CLIENT_PARAM_BLANK
message = "app_version is null"
elif not docker.check_app_websoft9(app_name):
code = const.ERROR_CLIENT_PARAM_NOTEXIST
message = "It is not support to install " + app_name
elif re.match('^[a-z0-9]+$', customer_name) == None:
code = const.ERROR_CLIENT_PARAM_Format
message = "APP name can only be composed of numbers and lowercase letters"
elif docker.check_directory("/data/apps/" + customer_name):
code = const.ERROR_CLIENT_PARAM_REPEAT
message = "Repeat installation: " + customer_name
2023-03-24 15:25:25 +08:00
elif not docker.check_vm_resource(app_name):
2023-04-14 13:15:35 +08:00
code = const.ERROR_SERVER_RESOURCE
message = "Insufficient system resources (cpu, memory, disk space)"
elif check_app_rq(app_id):
code = const.ERROR_CLIENT_PARAM_REPEAT
message = "Repeat installation: " + customer_name
2023-03-24 15:25:25 +08:00
return code, message
2023-03-15 16:13:17 +08:00
2023-04-14 19:42:42 +08:00
def prepare_app(app_name, customer_name):
2023-03-24 16:02:35 +08:00
library_path = "/data/library/apps/" + app_name
2023-04-14 16:41:43 +08:00
install_path = "/data/apps/" + customer_name
shell_execute.execute_command_output_all("cp -r " + library_path + " " + install_path)
2023-03-24 15:25:25 +08:00
2023-04-14 19:42:42 +08:00
def install_app_delay(app_name, customer_name, app_version):
2023-04-14 16:41:43 +08:00
job_id = app_name + "_" + customer_name
2023-04-14 14:36:37 +08:00
2023-04-06 14:37:26 +08:00
try:
2023-04-06 14:10:32 +08:00
2023-04-14 16:41:43 +08:00
code, message = check_app(app_name, customer_name, app_version)
if code == None:
prepare_app(app_name, customer_name)
myLogger.info_logger("start JobID=" + job_id)
# modify env
env_path = "/data/apps/" + customer_name + "/.env"
docker.modify_env(env_path, 'APP_NAME', customer_name)
docker.modify_env(env_path, "APP_VERSION", app_version)
# check port
docker.check_app_compose(env_path)
cmd = "cd /data/apps/" + customer_name + " && sudo docker compose pull && sudo docker compose up -d"
output = shell_execute.execute_command_output_all(cmd)
myLogger.info_logger("-------Install result--------")
myLogger.info_logger(output["code"])
myLogger.info_logger(output["result"])
2023-04-06 14:37:26 +08:00
else:
2023-04-14 19:42:42 +08:00
raise CommandException(code, message, "")
2023-04-14 16:41:43 +08:00
except CommandException as ce:
uninstall_app(job_id)
2023-04-14 19:42:42 +08:00
raise CommandException(ce.code, ce.message, ce.detail)
2023-04-06 14:37:26 +08:00
except Exception as e:
2023-04-14 16:41:43 +08:00
myLogger.info_logger(customer_name + "install failed!")
2023-04-06 14:37:26 +08:00
myLogger.error_logger(e)
2023-04-14 16:41:43 +08:00
uninstall_app(job_id)
2023-04-14 19:42:42 +08:00
raise CommandException(const.ERROR_SERVER_SYSTEM, "system original error", str(e))
2023-04-15 07:57:23 +08:00
def app_exits_in_docker(app_id):
customer_name = app_id.split('_')[1]
app_name = app_id.split('_')[0]
2023-03-31 16:44:16 +08:00
flag = False
2023-03-28 16:07:39 +08:00
info = ""
cmd = "docker compose ls -a | grep \'/" + app_name + "/\'"
2023-03-24 15:25:25 +08:00
output = shell_execute.execute_command_output_all(cmd)
2023-03-31 16:44:16 +08:00
if int(output["code"]) == 0:
2023-03-28 17:31:06 +08:00
info = output["result"]
2023-03-31 16:44:16 +08:00
app_path = info.split()[-1].rsplit('/', 1)[0]
is_official = check_if_official_app(app_path + '/variables.json')
if is_official:
name = docker.read_var(app_path + '/variables.json', 'name')
2023-04-15 07:57:23 +08:00
if name == app_name:
2023-03-31 16:44:16 +08:00
flag = True
2023-04-15 07:57:23 +08:00
elif app_name == customer_name:
2023-04-04 17:25:23 +08:00
flag = True
2023-03-31 16:44:16 +08:00
myLogger.info_logger("APP info: " + info)
return info, flag
2023-03-24 15:25:25 +08:00
2023-03-15 16:13:17 +08:00
def split_app_id(app_id):
2023-03-16 10:41:28 +08:00
return app_id.split("_")[1]
2023-03-24 15:23:39 +08:00
2023-03-27 10:24:54 +08:00
def get_apps_from_compose(output_list):
2023-03-24 15:23:39 +08:00
ip_result = shell_execute.execute_command_output_all("curl ifconfig.me")
ip = ip_result["result"]
app_list = []
has_add = []
for app_info in output_list:
volume = app_info["ConfigFiles"] # volume
2023-03-28 16:07:39 +08:00
app_path = volume.rsplit('/', 1)[0]
2023-04-14 17:23:17 +08:00
customer_name = volume.split('/')[-2]
app_id = "" # app_id
2023-04-14 14:36:37 +08:00
app_name = ""
2023-03-29 14:48:18 +08:00
trade_mark = ""
port = 0
url = ""
admin_url = ""
image_url = ""
user_name = ""
password = ""
official_app = False
2023-04-14 19:42:42 +08:00
if customer_name in ['appmanage', 'nginxproxymanager',
'redis'] and app_path == '/data/apps/stackhub/docker/' + customer_name:
2023-03-29 14:48:18 +08:00
continue
# get code
2023-04-14 19:42:42 +08:00
status = app_info["Status"].split("(")[0]
2023-04-14 17:34:19 +08:00
if status == "running" or status == "exited" or status == "restarting":
myLogger.info_logger("ok")
elif status == "created":
status = "failed"
2023-03-24 15:23:39 +08:00
else:
2023-04-14 17:34:19 +08:00
continue
2023-03-29 14:48:18 +08:00
var_path = app_path + "/variables.json"
official_app = check_if_official_app(var_path)
if official_app:
2023-04-14 14:36:37 +08:00
app_name = docker.read_var(var_path, 'name')
2023-04-14 17:23:17 +08:00
app_id = app_name + "_" + customer_name # app_id
2023-03-29 14:48:18 +08:00
# get trade_mark
trade_mark = docker.read_var(var_path, 'trademark')
2023-04-14 14:36:37 +08:00
image_url = get_Image_url(app_name)
2023-03-29 14:48:18 +08:00
# get env info
path = app_path + "/.env"
# get port and url
try:
http_port = list(docker.read_env(
path, "APP_HTTP_PORT").values())[0]
port = int(http_port)
easy_url = "http://" + ip + ":" + str(port)
2023-04-14 14:36:37 +08:00
url = get_url(app_name, easy_url)
admin_url = get_admin_url(app_name, url)
2023-03-29 14:48:18 +08:00
except IndexError:
try:
2023-04-14 17:23:17 +08:00
db_port = list(docker.read_env(path, "APP_DB.*_PORT").values())[0]
2023-03-29 14:48:18 +08:00
port = int(db_port)
except IndexError:
pass
# get user_name
try:
user_name = list(docker.read_env(path, "APP_USER").values())[0]
except IndexError:
pass
# get password
2023-03-24 15:23:39 +08:00
try:
2023-04-14 17:23:17 +08:00
password = list(docker.read_env(path, "POWER_PASSWORD").values())[0]
2023-03-24 15:23:39 +08:00
except IndexError:
pass
2023-04-14 17:23:17 +08:00
has_add.append(customer_name)
2023-04-14 14:36:37 +08:00
running_info = RunningInfo(port=port, compose_file=volume, url=url, image_url=image_url, admin_url=admin_url,
user_name=user_name, password=password, default_domain="", set_domain="")
status_reason = StatusReason(Code="", Message="", Detail="")
2023-04-14 17:34:19 +08:00
app = App(app_id=app_id, name=app_name, customer_name=customer_name, trade_mark=trade_mark, status=status,
2023-04-14 14:36:37 +08:00
official_app=official_app, running_info=running_info, status_reason=status_reason)
2023-03-24 15:23:39 +08:00
app_list.append(app.dict())
2023-03-27 10:24:54 +08:00
return app_list, has_add
2023-03-24 15:23:39 +08:00
2023-04-14 19:42:42 +08:00
2023-03-29 14:48:18 +08:00
def check_if_official_app(var_path):
if docker.check_directory(var_path):
if docker.read_var(var_path, 'name') != "" and docker.read_var(var_path, 'trademark') != "" and docker.read_var(
var_path, 'requirements') != "":
2023-03-30 10:22:26 +08:00
requirements = docker.read_var(var_path, 'requirements')
try:
cpu = requirements['cpu']
mem = requirements['memory']
return True
except:
return False
2023-03-29 14:48:18 +08:00
else:
return False
2023-04-08 07:59:03 +08:00
2023-04-14 19:42:42 +08:00
def check_app_rq(app_id):
2023-04-14 17:23:17 +08:00
myLogger.info_logger("check_app_rq")
2023-04-14 19:42:42 +08:00
2023-04-14 17:23:17 +08:00
for job in q.jobs:
if app_id == job.id:
return True
started = StartedJobRegistry(queue=q)
failed = FailedJobRegistry(queue=q)
run_job_ids = started.get_job_ids()
failed_jobs = failed.get_job_ids()
if app_id in run_job_ids:
return True
if app_id in failed_jobs:
return True
2023-04-14 19:42:42 +08:00
2023-04-14 17:23:17 +08:00
return False
2023-04-14 14:36:37 +08:00
2023-04-14 19:42:42 +08:00
2023-04-04 12:14:00 +08:00
def get_apps_from_queue():
2023-04-06 07:57:10 +08:00
myLogger.info_logger("get queque apps...")
2023-04-04 12:14:00 +08:00
# 获取 StartedJobRegistry 实例
2023-04-14 17:23:17 +08:00
started = StartedJobRegistry(queue=q)
2023-04-04 12:14:00 +08:00
finish = FinishedJobRegistry(queue=q)
deferred = DeferredJobRegistry(queue=q)
2023-04-06 13:06:41 +08:00
failed = FailedJobRegistry(queue=q)
scheduled = ScheduledJobRegistry(queue=q)
2023-04-14 19:42:42 +08:00
cancel = CanceledJobRegistry(queue=q)
2023-04-06 13:06:41 +08:00
2023-04-04 12:14:00 +08:00
# 获取正在执行的作业 ID 列表
2023-04-14 17:23:17 +08:00
run_job_ids = started.get_job_ids()
2023-04-04 12:14:00 +08:00
finish_job_ids = finish.get_job_ids()
wait_job_ids = deferred.get_job_ids()
2023-04-06 13:06:41 +08:00
failed_jobs = failed.get_job_ids()
scheduled_jobs = scheduled.get_job_ids()
2023-04-14 17:23:17 +08:00
cancel_jobs = cancel.get_job_ids()
2023-04-06 13:06:41 +08:00
myLogger.info_logger(q.jobs)
2023-04-06 07:57:10 +08:00
myLogger.info_logger(run_job_ids)
2023-04-07 09:52:22 +08:00
myLogger.info_logger(failed_jobs)
2023-04-14 17:23:17 +08:00
myLogger.info_logger(cancel_jobs)
myLogger.info_logger(wait_job_ids)
myLogger.info_logger(finish_job_ids)
2023-04-06 13:06:41 +08:00
myLogger.info_logger(scheduled_jobs)
2023-04-07 09:52:22 +08:00
2023-04-04 17:25:23 +08:00
installing_list = []
2023-04-14 17:23:17 +08:00
for job_id in run_job_ids:
2023-04-14 19:42:42 +08:00
app = get_installing_app(job_id, 'installing', '""', "", "")
2023-04-04 17:25:23 +08:00
installing_list.append(app)
2023-04-07 09:52:22 +08:00
for job in q.jobs:
2023-04-14 19:42:42 +08:00
app = get_installing_app(job.id, 'installing', "", "", "")
2023-04-04 17:25:23 +08:00
installing_list.append(app)
2023-04-14 17:23:17 +08:00
for job_id in failed_jobs:
2023-04-14 19:42:42 +08:00
job = q.fetch_job(job_id)
app = get_installing_app(job_id, 'failed', "", "", "")
2023-04-14 17:34:19 +08:00
installing_list.append(app)
2023-04-07 09:52:22 +08:00
2023-04-04 17:25:23 +08:00
return installing_list
2023-04-14 19:42:42 +08:00
def get_installing_app(id, status, code, message, detail):
2023-04-14 14:36:37 +08:00
app_name = id.split('_')[0]
2023-04-14 17:23:17 +08:00
customer_name = id.split('_')[1]
var_path = "/data/apps/" + customer_name + "/variables.json"
2023-04-04 17:25:23 +08:00
trade_mark = docker.read_var(var_path, 'trademark')
2023-04-14 14:36:37 +08:00
app_name = docker.read_var(var_path, 'name')
image_url = get_Image_url(app_name)
running_info = RunningInfo(port=0, compose_file="", url="", image_url=image_url, admin_url="",
user_name="", password="", default_domain="", set_domain="")
2023-04-14 17:23:17 +08:00
status_reason = StatusReason(Code=code, Message=message, Detail=detail)
app = App(app_id=app_name + "_" + customer_name, name=app_name, customer_name=customer_name,
2023-04-14 19:42:42 +08:00
trade_mark=trade_mark, status=status, official_app=True, running_info=running_info,
status_reason=status_reason)
2023-04-04 17:25:23 +08:00
return app
2023-03-24 15:23:39 +08:00
2023-04-14 19:42:42 +08:00
2023-03-25 20:31:44 +08:00
def get_Image_url(app_name):
2023-03-30 13:19:54 +08:00
image_url = "static/images/" + app_name + "-websoft9.png"
2023-03-24 15:23:39 +08:00
return image_url
2023-03-25 20:31:44 +08:00
def get_url(app_name, easy_url):
2023-03-24 15:23:39 +08:00
url = easy_url
if app_name == "joomla":
url = easy_url + "/administrator"
elif app_name == "other":
url = easy_url + "/administrator"
else:
url = easy_url
return url
2023-04-04 17:25:23 +08:00
2023-03-24 15:23:39 +08:00
def get_admin_url(app_name, url):
2023-03-29 14:48:18 +08:00
admin_url = ""
2023-03-24 15:23:39 +08:00
if app_name == "wordpress":
admin_url = url + "/wp-admin"
elif app_name == "other":
admin_url = url + "/admin"
else:
2023-03-29 14:48:18 +08:00
admin_url = ""
2023-03-24 15:23:39 +08:00
return admin_url
2023-04-14 09:27:08 +08:00
2023-04-14 10:51:29 +08:00
def get_error_info(code, message, detail):
2023-04-14 09:27:08 +08:00
error = {}
2023-04-14 10:51:29 +08:00
error['Code'] = code
error['Message'] = message
error['Detail'] = detail
2023-04-14 14:36:37 +08:00
return error