From 6c77c699aa020bf7495b0b1b66cb83fde1c021de Mon Sep 17 00:00:00 2001 From: vapao Date: Fri, 20 Aug 2021 00:56:10 +0800 Subject: [PATCH] fix issue --- spug_api/apps/deploy/helper.py | 212 ++++++++++++++++ spug_api/apps/deploy/models.py | 8 + spug_api/apps/deploy/utils.py | 237 +++--------------- spug_api/apps/deploy/views.py | 35 ++- spug_api/apps/repository/utils.py | 84 ++----- .../src/pages/deploy/request/Ext1Console.js | 29 ++- 6 files changed, 318 insertions(+), 287 deletions(-) create mode 100644 spug_api/apps/deploy/helper.py diff --git a/spug_api/apps/deploy/helper.py b/spug_api/apps/deploy/helper.py new file mode 100644 index 0000000..8072054 --- /dev/null +++ b/spug_api/apps/deploy/helper.py @@ -0,0 +1,212 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Copyright: (c) +# Released under the AGPL-3.0 License. +from django_redis import get_redis_connection +from django.conf import settings +from libs.utils import human_datetime +from apps.host.models import Host +from apps.notify.models import Notify +import requests +import subprocess +import json +import os + + +class SpugError(Exception): + pass + + +class Helper: + def __init__(self, rds, key): + self.rds = rds + self.key = key + self.rds.delete(self.key) + self.by_deploy = key.startswith(settings.REQUEST_KEY) + + @classmethod + def _make_dd_notify(cls, action, req, version, host_str): + texts = [ + f'**申请标题:** {req.name}', + f'**应用名称:** {req.deploy.app.name}', + f'**应用版本:** {version}', + f'**发布环境:** {req.deploy.env.name}', + f'**发布主机:** {host_str}', + ] + if action == 'approve_req': + texts.insert(0, '## %s ## ' % '发布审核申请') + texts.extend([ + f'**申请人员:** {req.created_by.nickname}', + f'**申请时间:** {human_datetime()}', + '> 来自 Spug运维平台' + ]) + elif action == 'approve_rst': + color, text = ('#008000', '通过') if req.status == '1' else ('#f90202', '驳回') + texts.insert(0, '## %s ## ' % '发布审核结果') + texts.extend([ + f'**审核人员:** {req.approve_by.nickname}', + f'**审核结果:** {text}', + f'**审核意见:** {req.reason or ""}', + f'**审核时间:** {human_datetime()}', + '> 来自 Spug运维平台' + ]) + else: + color, text = ('#008000', '成功') if req.status == '3' else ('#f90202', '失败') + texts.insert(0, '## %s ## ' % '发布结果通知') + if req.approve_at: + texts.append(f'**审核人员:** {req.approve_by.nickname}') + do_user = req.do_by.nickname if req.type != '3' else 'Webhook' + texts.extend([ + f'**执行人员:** {do_user}', + f'**发布结果:** {text}', + f'**发布时间:** {human_datetime()}', + '> 来自 Spug运维平台' + ]) + return { + 'msgtype': 'markdown', + 'markdown': { + 'title': 'Spug 发布消息通知', + 'text': '\n\n'.join(texts) + } + } + + @classmethod + def _make_wx_notify(cls, action, req, version, host_str): + texts = [ + f'申请标题: {req.name}', + f'应用名称: {req.deploy.app.name}', + f'应用版本: {version}', + f'发布环境: {req.deploy.env.name}', + f'发布主机: {host_str}', + ] + + if action == 'approve_req': + texts.insert(0, '## %s' % '发布审核申请') + texts.extend([ + f'申请人员: {req.created_by.nickname}', + f'申请时间: {human_datetime()}', + '> 来自 Spug运维平台' + ]) + elif action == 'approve_rst': + color, text = ('info', '通过') if req.status == '1' else ('warning', '驳回') + texts.insert(0, '## %s' % '发布审核结果') + texts.extend([ + f'审核人员: {req.approve_by.nickname}', + f'审核结果: {text}', + f'审核意见: {req.reason or ""}', + f'审核时间: {human_datetime()}', + '> 来自 Spug运维平台' + ]) + else: + color, text = ('info', '成功') if req.status == '3' else ('warning', '失败') + texts.insert(0, '## %s' % '发布结果通知') + if req.approve_at: + texts.append(f'审核人员: {req.approve_by.nickname}') + do_user = req.do_by.nickname if req.type != '3' else 'Webhook' + texts.extend([ + f'执行人员: {do_user}', + f'发布结果: {text}', + f'发布时间: {human_datetime()}', + '> 来自 Spug运维平台' + ]) + return { + 'msgtype': 'markdown', + 'markdown': { + 'content': '\n'.join(texts) + } + } + + @classmethod + def send_deploy_notify(cls, req, action=None): + rst_notify = json.loads(req.deploy.rst_notify) + host_ids = json.loads(req.host_ids) + if rst_notify['mode'] != '0' and rst_notify.get('value'): + version = req.version + hosts = [{'id': x.id, 'name': x.name} for x in Host.objects.filter(id__in=host_ids)] + host_str = ', '.join(x['name'] for x in hosts[:2]) + if len(hosts) > 2: + host_str += f'等{len(hosts)}台主机' + if rst_notify['mode'] == '1': + data = cls._make_dd_notify(action, req, version, host_str) + elif rst_notify['mode'] == '2': + data = { + 'action': action, + 'req_id': req.id, + 'req_name': req.name, + 'app_id': req.deploy.app_id, + 'app_name': req.deploy.app.name, + 'env_id': req.deploy.env_id, + 'env_name': req.deploy.env.name, + 'status': req.status, + 'reason': req.reason, + 'version': version, + 'targets': hosts, + 'is_success': req.status == '3', + 'created_at': human_datetime() + } + elif rst_notify['mode'] == '3': + data = cls._make_wx_notify(action, req, version, host_str) + else: + raise NotImplementedError + res = requests.post(rst_notify['value'], json=data) + if res.status_code != 200: + Notify.make_notify('flag', '1', '发布通知发送失败', f'返回状态码:{res.status_code}, 请求URL:{res.url}') + if rst_notify['mode'] in ['1', '3']: + res = res.json() + if res.get('errcode') != 0: + Notify.make_notify('flag', '1', '发布通知发送失败', f'返回数据:{res}') + + def parse_filter_rule(self, data: str, sep='\n'): + data, files = data.strip(), [] + if data: + for line in data.split(sep): + line = line.strip() + if line and not line.startswith('#'): + files.append(line) + return files + + def _send(self, message): + self.rds.rpush(self.key, json.dumps(message)) + + def send_info(self, key, message): + if message: + self._send({'key': key, 'data': message}) + + def send_error(self, key, message, with_break=True): + message = f'\r\n\033[31m{message}\033[0m' + self._send({'key': key, 'status': 'error', 'data': message}) + if with_break: + raise SpugError + + def send_step(self, key, step, data): + self._send({'key': key, 'step': step, 'data': data}) + + def clear(self): + # save logs for two weeks + self.rds.expire(self.key, 14 * 24 * 60 * 60) + self.rds.close() + + def local(self, command, env=None): + if env: + env = dict(env.items()) + env.update(os.environ) + task = subprocess.Popen(command, env=env, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + while True: + message = task.stdout.readline() + if not message: + break + message = message.decode().rstrip('\r\n') + self.send_info('local', message + '\r\n') + if task.wait() != 0: + self.send_error('local', f'exit code: {task.returncode}') + + def remote(self, key, ssh, command, env=None): + code = -1 + for code, out in ssh.exec_command_with_stream(command, environment=env): + self.send_info(key, out) + if code != 0: + self.send_error(key, f'exit code: {code}') + + def remote_raw(self, key, ssh, command): + code, out = ssh.exec_command_raw(command) + if code != 0: + self.send_error(key, f'exit code: {code}') diff --git a/spug_api/apps/deploy/models.py b/spug_api/apps/deploy/models.py index b4b2f68..c55d5d8 100644 --- a/spug_api/apps/deploy/models.py +++ b/spug_api/apps/deploy/models.py @@ -6,6 +6,7 @@ from libs import ModelMixin, human_datetime from apps.account.models import User from apps.app.models import Deploy from apps.repository.models import Repository +import json class DeployRequest(models.Model, ModelMixin): @@ -42,6 +43,13 @@ class DeployRequest(models.Model, ModelMixin): do_at = models.CharField(max_length=20, null=True) do_by = models.ForeignKey(User, models.PROTECT, related_name='+', null=True) + @property + def is_quick_deploy(self): + if self.extra: + extra = json.loads(self.extra) + return extra[0] in ('branch', 'tag') + return False + def __repr__(self): return f'' diff --git a/spug_api/apps/deploy/utils.py b/spug_api/apps/deploy/utils.py index fe43531..ad31a88 100644 --- a/spug_api/apps/deploy/utils.py +++ b/spug_api/apps/deploy/utils.py @@ -3,13 +3,14 @@ # Released under the AGPL-3.0 License. from django_redis import get_redis_connection from django.conf import settings -from libs.utils import AttrDict, human_time, human_datetime +from django.db import close_old_connections +from libs.utils import AttrDict, human_time from apps.host.models import Host -from apps.notify.models import Notify from apps.config.utils import compose_configs +from apps.repository.models import Repository +from apps.repository.utils import dispatch as build_repository +from apps.deploy.helper import Helper, SpugError from concurrent import futures -import requests -import subprocess import json import uuid import os @@ -17,17 +18,13 @@ import os REPOS_DIR = settings.REPOS_DIR -class SpugError(Exception): - pass - - def dispatch(req): rds = get_redis_connection() rds_key = f'{settings.REQUEST_KEY}:{req.id}' + helper = Helper(rds, rds_key) try: api_token = uuid.uuid4().hex rds.setex(api_token, 60 * 60, f'{req.deploy.app_id},{req.deploy.env_id}') - helper = Helper(rds, rds_key) env = AttrDict( SPUG_APP_NAME=req.deploy.app.name, SPUG_APP_ID=str(req.deploy.app_id), @@ -55,13 +52,25 @@ def dispatch(req): req.status = '-3' raise e finally: - rds.expire(rds_key, 14 * 24 * 60 * 60) - rds.close() + close_old_connections() req.save() + helper.clear() Helper.send_deploy_notify(req) def _ext1_deploy(req, helper, env): + if not req.repository_id: + rep = Repository( + app_id=req.deploy.app_id, + env_id=req.deploy.env_id, + deploy_id=req.deploy_id, + version=req.version, + spug_version=req.spug_version, + extra=req.extra, + created_by_id=req.created_by_id + ) + build_repository(rep, helper) + req.repository = rep extend = req.deploy.extend_obj env.update(SPUG_DST_DIR=extend.dst_dir) threads, latest_exception = [], None @@ -83,7 +92,7 @@ def _ext1_deploy(req, helper, env): def _ext2_deploy(req, helper, env): - helper.send_info('local', f'完成\r\n') + helper.send_info('local', f'\033[32m完成√\033[0m\r\n') extend, step = req.deploy.extend_obj, 1 host_actions = json.loads(extend.host_actions) server_actions = json.loads(extend.server_actions) @@ -125,7 +134,7 @@ def _ext2_deploy(req, helper, env): exclude = ' '.join(excludes) tar_gz_file = f'{req.spug_version}.tar.gz' helper.local(f'cd {sp_dir} && tar -zcf {tar_gz_file} {exclude} {contain}') - helper.send_info('local', f'{human_time()} 打包完成\r\n') + helper.send_info('local', f'{human_time()} \033[32m完成√\033[0m\r\n') tmp_transfer_file = os.path.join(sp_dir, tar_gz_file) break if host_actions: @@ -153,7 +162,7 @@ def _ext2_deploy(req, helper, env): def _deploy_ext1_host(req, helper, h_id, env): extend = req.deploy.extend_obj - helper.send_step(h_id, 1, f'就绪\r\n{human_time()} 数据准备... ') + helper.send_step(h_id, 1, f'\033[32m就绪√\033[0m\r\n{human_time()} 数据准备... ') host = Host.objects.filter(pk=h_id).first() if not host: helper.send_error(h_id, 'no such host') @@ -175,7 +184,7 @@ def _deploy_ext1_host(req, helper, h_id, env): command = f'cd {extend.dst_repo} && rm -rf {req.spug_version} && tar xf {tar_gz_file} && rm -f {req.deploy_id}_*.tar.gz' helper.remote_raw(host.id, ssh, command) - helper.send_step(h_id, 1, '完成\r\n') + helper.send_step(h_id, 1, '\033[32m完成√\033[0m\r\n') # pre host repo_dir = os.path.join(extend.dst_repo, req.spug_version) @@ -187,7 +196,7 @@ def _deploy_ext1_host(req, helper, h_id, env): # do deploy helper.send_step(h_id, 3, f'{human_time()} 执行发布... ') helper.remote_raw(host.id, ssh, f'rm -f {extend.dst_dir} && ln -sfn {repo_dir} {extend.dst_dir}') - helper.send_step(h_id, 3, '完成\r\n') + helper.send_step(h_id, 3, '\033[32m完成√\033[0m\r\n') # post host if extend.hook_post_host: @@ -195,11 +204,11 @@ def _deploy_ext1_host(req, helper, h_id, env): command = f'cd {extend.dst_dir} ; {extend.hook_post_host}' helper.remote(host.id, ssh, command) - helper.send_step(h_id, 100, f'\r\n{human_time()} ** 发布成功 **') + helper.send_step(h_id, 100, f'\r\n{human_time()} ** \033[32m发布成功\033[0m **') def _deploy_ext2_host(helper, h_id, actions, env, spug_version): - helper.send_info(h_id, '就绪\r\n') + helper.send_info(h_id, '\033[32m就绪√\033[0m\r\n') host = Host.objects.filter(pk=h_id).first() if not host: helper.send_error(h_id, 'no such host') @@ -230,194 +239,4 @@ def _deploy_ext2_host(helper, h_id, actions, env, spug_version): command = f'cd /tmp ; {action["data"]}' helper.remote(host.id, ssh, command) - helper.send_step(h_id, 100, f'\r\n{human_time()} ** 发布成功 **') - - -class Helper: - def __init__(self, rds, key): - self.rds = rds - self.key = key - self.rds.delete(self.key) - - @classmethod - def _make_dd_notify(cls, action, req, version, host_str): - texts = [ - f'**申请标题:** {req.name}', - f'**应用名称:** {req.deploy.app.name}', - f'**应用版本:** {version}', - f'**发布环境:** {req.deploy.env.name}', - f'**发布主机:** {host_str}', - ] - if action == 'approve_req': - texts.insert(0, '## %s ## ' % '发布审核申请') - texts.extend([ - f'**申请人员:** {req.created_by.nickname}', - f'**申请时间:** {human_datetime()}', - '> 来自 Spug运维平台' - ]) - elif action == 'approve_rst': - color, text = ('#008000', '通过') if req.status == '1' else ('#f90202', '驳回') - texts.insert(0, '## %s ## ' % '发布审核结果') - texts.extend([ - f'**审核人员:** {req.approve_by.nickname}', - f'**审核结果:** {text}', - f'**审核意见:** {req.reason or ""}', - f'**审核时间:** {human_datetime()}', - '> 来自 Spug运维平台' - ]) - else: - color, text = ('#008000', '成功') if req.status == '3' else ('#f90202', '失败') - texts.insert(0, '## %s ## ' % '发布结果通知') - if req.approve_at: - texts.append(f'**审核人员:** {req.approve_by.nickname}') - do_user = req.do_by.nickname if req.type != '3' else 'Webhook' - texts.extend([ - f'**执行人员:** {do_user}', - f'**发布结果:** {text}', - f'**发布时间:** {human_datetime()}', - '> 来自 Spug运维平台' - ]) - return { - 'msgtype': 'markdown', - 'markdown': { - 'title': 'Spug 发布消息通知', - 'text': '\n\n'.join(texts) - } - } - - @classmethod - def _make_wx_notify(cls, action, req, version, host_str): - texts = [ - f'申请标题: {req.name}', - f'应用名称: {req.deploy.app.name}', - f'应用版本: {version}', - f'发布环境: {req.deploy.env.name}', - f'发布主机: {host_str}', - ] - - if action == 'approve_req': - texts.insert(0, '## %s' % '发布审核申请') - texts.extend([ - f'申请人员: {req.created_by.nickname}', - f'申请时间: {human_datetime()}', - '> 来自 Spug运维平台' - ]) - elif action == 'approve_rst': - color, text = ('info', '通过') if req.status == '1' else ('warning', '驳回') - texts.insert(0, '## %s' % '发布审核结果') - texts.extend([ - f'审核人员: {req.approve_by.nickname}', - f'审核结果: {text}', - f'审核意见: {req.reason or ""}', - f'审核时间: {human_datetime()}', - '> 来自 Spug运维平台' - ]) - else: - color, text = ('info', '成功') if req.status == '3' else ('warning', '失败') - texts.insert(0, '## %s' % '发布结果通知') - if req.approve_at: - texts.append(f'审核人员: {req.approve_by.nickname}') - do_user = req.do_by.nickname if req.type != '3' else 'Webhook' - texts.extend([ - f'执行人员: {do_user}', - f'发布结果: {text}', - f'发布时间: {human_datetime()}', - '> 来自 Spug运维平台' - ]) - return { - 'msgtype': 'markdown', - 'markdown': { - 'content': '\n'.join(texts) - } - } - - @classmethod - def send_deploy_notify(cls, req, action=None): - rst_notify = json.loads(req.deploy.rst_notify) - host_ids = json.loads(req.host_ids) - if rst_notify['mode'] != '0' and rst_notify.get('value'): - version = req.version - hosts = [{'id': x.id, 'name': x.name} for x in Host.objects.filter(id__in=host_ids)] - host_str = ', '.join(x['name'] for x in hosts[:2]) - if len(hosts) > 2: - host_str += f'等{len(hosts)}台主机' - if rst_notify['mode'] == '1': - data = cls._make_dd_notify(action, req, version, host_str) - elif rst_notify['mode'] == '2': - data = { - 'action': action, - 'req_id': req.id, - 'req_name': req.name, - 'app_id': req.deploy.app_id, - 'app_name': req.deploy.app.name, - 'env_id': req.deploy.env_id, - 'env_name': req.deploy.env.name, - 'status': req.status, - 'reason': req.reason, - 'version': version, - 'targets': hosts, - 'is_success': req.status == '3', - 'created_at': human_datetime() - } - elif rst_notify['mode'] == '3': - data = cls._make_wx_notify(action, req, version, host_str) - else: - raise NotImplementedError - res = requests.post(rst_notify['value'], json=data) - if res.status_code != 200: - Notify.make_notify('flag', '1', '发布通知发送失败', f'返回状态码:{res.status_code}, 请求URL:{res.url}') - if rst_notify['mode'] in ['1', '3']: - res = res.json() - if res.get('errcode') != 0: - Notify.make_notify('flag', '1', '发布通知发送失败', f'返回数据:{res}') - - def parse_filter_rule(self, data: str, sep='\n'): - data, files = data.strip(), [] - if data: - for line in data.split(sep): - line = line.strip() - if line and not line.startswith('#'): - files.append(line) - return files - - def _send(self, message): - self.rds.rpush(self.key, json.dumps(message)) - - def send_info(self, key, message): - if message: - self._send({'key': key, 'data': message}) - - def send_error(self, key, message, with_break=True): - message = '\r\n' + message - self._send({'key': key, 'status': 'error', 'data': message}) - if with_break: - raise SpugError - - def send_step(self, key, step, data): - self._send({'key': key, 'step': step, 'data': data}) - - def local(self, command, env=None): - if env: - env = dict(env.items()) - env.update(os.environ) - task = subprocess.Popen(command, env=env, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - while True: - message = task.stdout.readline() - if not message: - break - message = message.decode().rstrip('\r\n') - self.send_info('local', message + '\r\n') - if task.wait() != 0: - self.send_error('local', f'exit code: {task.returncode}') - - def remote(self, key, ssh, command, env=None): - code = -1 - for code, out in ssh.exec_command_with_stream(command, environment=env): - self.send_info(key, out) - if code != 0: - self.send_error(key, f'exit code: {code}') - - def remote_raw(self, key, ssh, command): - code, out = ssh.exec_command_raw(command) - if code != 0: - self.send_error(key, f'exit code: {code}') + helper.send_step(h_id, 100, f'\r\n{human_time()} ** \033[32m发布成功\033[0m **') diff --git a/spug_api/apps/deploy/views.py b/spug_api/apps/deploy/views.py index de163ad..e3d506d 100644 --- a/spug_api/apps/deploy/views.py +++ b/spug_api/apps/deploy/views.py @@ -126,12 +126,15 @@ class RequestDetailView(View): hosts = Host.objects.filter(id__in=json.loads(req.host_ids)) outputs = {x.id: {'id': x.id, 'title': x.name, 'data': f'{human_time()} 读取数据... '} for x in hosts} response = {'outputs': outputs, 'status': req.status} + if req.is_quick_deploy: + outputs['local'] = {'id': 'local', 'data': ''} if req.deploy.extend == '2': outputs['local'] = {'id': 'local', 'data': f'{human_time()} 读取数据... '} - response['s_actions'] = json.loads(req.deploy.extend_obj.server_actions) - response['h_actions'] = json.loads(req.deploy.extend_obj.host_actions) - if not response['h_actions']: - response['outputs'] = {'local': outputs['local']} + if req.deploy.extend == '2': + response['s_actions'] = json.loads(req.deploy.extend_obj.server_actions) + response['h_actions'] = json.loads(req.deploy.extend_obj.host_actions) + if not response['h_actions']: + response['outputs'] = {'local': outputs['local']} rds, key, counter = get_redis_connection(), f'{settings.REQUEST_KEY}:{r_id}', 0 data = rds.lrange(key, counter, counter + 9) while data: @@ -146,6 +149,11 @@ class RequestDetailView(View): outputs[item['key']]['status'] = item['status'] data = rds.lrange(key, counter, counter + 9) response['index'] = counter + if req.is_quick_deploy: + if outputs['local']['data']: + outputs['local']['data'] = f'{human_time()} 读取数据... ' + outputs['local']['data'] + else: + outputs['local'].update(step=100, data=f'{human_time()} 已构建完成忽略执行。') return json_response(response) def post(self, request, r_id): @@ -167,14 +175,19 @@ class RequestDetailView(View): req.do_by = request.user req.save() Thread(target=dispatch, args=(req,)).start() + if req.is_quick_deploy: + if req.repository_id: + outputs['local'] = {'id': 'local', 'step': 100, 'data': f'{human_time()} 已构建完成忽略执行。'} + else: + outputs['local'] = {'id': 'local', 'step': 0, 'data': f'{human_time()} 建立连接... '} if req.deploy.extend == '2': - message = f'{human_time()} 建立连接... ' - outputs['local'] = {'id': 'local', 'step': 0, 'data': message} - s_actions = json.loads(req.deploy.extend_obj.server_actions) - h_actions = json.loads(req.deploy.extend_obj.host_actions) - if not h_actions: - outputs = {'local': outputs['local']} - return json_response({'s_actions': s_actions, 'h_actions': h_actions, 'outputs': outputs}) + outputs['local'] = {'id': 'local', 'step': 0, 'data': f'{human_time()} 建立连接... '} + if req.deploy.extend == '2': + s_actions = json.loads(req.deploy.extend_obj.server_actions) + h_actions = json.loads(req.deploy.extend_obj.host_actions) + if not h_actions: + outputs = {'local': outputs['local']} + return json_response({'s_actions': s_actions, 'h_actions': h_actions, 'outputs': outputs}) return json_response({'outputs': outputs}) def patch(self, request, r_id): diff --git a/spug_api/apps/repository/utils.py b/spug_api/apps/repository/utils.py index e9c0099..abfb5a3 100644 --- a/spug_api/apps/repository/utils.py +++ b/spug_api/apps/repository/utils.py @@ -8,7 +8,7 @@ from libs.utils import AttrDict, human_time from apps.repository.models import Repository from apps.app.utils import fetch_repo from apps.config.utils import compose_configs -import subprocess +from apps.deploy.helper import Helper import json import uuid import os @@ -16,20 +16,18 @@ import os REPOS_DIR = settings.REPOS_DIR -class SpugError(Exception): - pass - - -def dispatch(rep: Repository): - rds = get_redis_connection() - rds_key = f'{settings.BUILD_KEY}:{rep.spug_version}' +def dispatch(rep: Repository, helper=None): rep.status = '1' - rep.save() - helper = Helper(rds, rds_key) + alone_build = helper is None + if not helper: + rds = get_redis_connection() + rds_key = f'{settings.BUILD_KEY}:{rep.spug_version}' + helper = Helper(rds, rds_key) + rep.save() try: api_token = uuid.uuid4().hex - rds.setex(api_token, 60 * 60, f'{rep.app_id},{rep.env_id}') - helper.send_info('local', f'完成\r\n{human_time()} 构建准备... ') + helper.rds.setex(api_token, 60 * 60, f'{rep.app_id},{rep.env_id}') + helper.send_info('local', f'\033[32m完成√\033[0m\r\n{human_time()} 构建准备... ') env = AttrDict( SPUG_APP_NAME=rep.app.name, SPUG_APP_ID=str(rep.app_id), @@ -54,11 +52,11 @@ def dispatch(rep: Repository): finally: helper.local(f'cd {REPOS_DIR} && rm -rf {rep.spug_version}') close_old_connections() - # save the build log for two weeks - rds.expire(rds_key, 14 * 24 * 60 * 60) - rds.close() - rep.save() - return rep + if alone_build: + helper.clear() + rep.save() + elif rep.status == '5': + rep.save() def _build(rep: Repository, helper, env): @@ -75,7 +73,7 @@ def _build(rep: Repository, helper, env): tree_ish = extras[1] env.update(SPUG_GIT_TAG=extras[1]) fetch_repo(rep.deploy_id, extend.git_repo) - helper.send_info('local', '完成\r\n') + helper.send_info('local', '\033[32m完成√\033[0m\r\n') if extend.hook_pre_server: helper.send_step('local', 1, f'{human_time()} 检出前任务...\r\n') @@ -84,7 +82,7 @@ def _build(rep: Repository, helper, env): helper.send_step('local', 2, f'{human_time()} 执行检出... ') command = f'cd {git_dir} && git archive --prefix={rep.spug_version}/ {tree_ish} | (cd .. && tar xf -)' helper.local(command) - helper.send_info('local', '完成\r\n') + helper.send_info('local', '\033[32m完成√\033[0m\r\n') if extend.hook_post_server: helper.send_step('local', 3, f'{human_time()} 检出后任务...\r\n') @@ -105,49 +103,5 @@ def _build(rep: Repository, helper, env): else: contain = ' '.join(f'{rep.spug_version}/{x}' for x in files) helper.local(f'cd {REPOS_DIR} && tar zcf {tar_file} {exclude} {contain}') - helper.send_step('local', 5, f'完成') - - -class Helper: - def __init__(self, rds, key): - self.rds = rds - self.key = key - self.rds.delete(self.key) - - def parse_filter_rule(self, data: str, sep='\n'): - data, files = data.strip(), [] - if data: - for line in data.split(sep): - line = line.strip() - if line and not line.startswith('#'): - files.append(line) - return files - - def _send(self, message): - self.rds.rpush(self.key, json.dumps(message)) - - def send_info(self, key, message): - self._send({'key': key, 'data': message}) - - def send_error(self, key, message, with_break=True): - message = '\r\n' + message - self._send({'key': key, 'status': 'error', 'data': message}) - if with_break: - raise SpugError - - def send_step(self, key, step, data): - self._send({'key': key, 'step': step, 'data': data}) - - def local(self, command, env=None): - if env: - env = dict(env.items()) - env.update(os.environ) - task = subprocess.Popen(command, env=env, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - while True: - message = task.stdout.readline() - if not message: - break - message = message.decode().rstrip('\r\n') - self.send_info('local', message + '\r\n') - if task.wait() != 0: - self.send_error('local', f'exit code: {task.returncode}') + helper.send_step('local', 5, f'\033[32m完成√\033[0m') + helper.send_step('local', 100, f'\r\n\r\n{human_time()} ** \033[32m构建成功\033[0m **') diff --git a/spug_web/src/pages/deploy/request/Ext1Console.js b/spug_web/src/pages/deploy/request/Ext1Console.js index b4235b2..79ad198 100644 --- a/spug_web/src/pages/deploy/request/Ext1Console.js +++ b/spug_web/src/pages/deploy/request/Ext1Console.js @@ -86,6 +86,7 @@ function Ext1Console(props) { terms[key] = term } + let {local, ...hosts} = outputs; return store.tabModes[props.request.id] ? ( {props.request.name} store.showConsole(props.request, true)}/> - {Object.values(outputs).map(item => ( + {local && ( + + )} + {Object.values(hosts).map(item => ( ]}> + {local && ( + + + + + + + + + + + + )}> + handleSetTerm(term, 'local')}/> + + + )} + }> - {Object.entries(outputs).map(([key, item], index) => ( + {Object.entries(hosts).map(([key, item], index) => (