fix issue

pull/410/head
vapao 2021-08-20 00:56:10 +08:00
parent 083452dd90
commit 6c77c699aa
6 changed files with 318 additions and 287 deletions

View File

@ -0,0 +1,212 @@
# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
# Copyright: (c) <spug.dev@gmail.com>
# 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'**审核结果:** <font color="{color}">{text}</font>',
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'**发布结果:** <font color="{color}">{text}</font>',
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'审核结果: <font color="{color}">{text}</font>',
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'发布结果: <font color="{color}">{text}</font>',
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}')

View File

@ -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'<DeployRequest name={self.name}>'

View File

@ -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'**审核结果:** <font color="{color}">{text}</font>',
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'**发布结果:** <font color="{color}">{text}</font>',
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'审核结果: <font color="{color}">{text}</font>',
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'发布结果: <font color="{color}">{text}</font>',
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 **')

View File

@ -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):

View File

@ -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 **')

View File

@ -86,6 +86,7 @@ function Ext1Console(props) {
terms[key] = term
}
let {local, ...hosts} = outputs;
return store.tabModes[props.request.id] ? (
<Card
className={styles.item}
@ -95,7 +96,12 @@ function Ext1Console(props) {
<div className={styles.title}>{props.request.name}</div>
<CloseOutlined onClick={() => store.showConsole(props.request, true)}/>
</div>
{Object.values(outputs).map(item => (
{local && (
<Progress
percent={(local.step + 1) * 18}
status={local.step === 100 ? 'success' : outputs.local.status === 'error' ? 'exception' : 'active'}/>
)}
{Object.values(hosts).map(item => (
<Progress
key={item.id}
percent={(item.step + 1) * 18}
@ -117,11 +123,30 @@ function Ext1Console(props) {
</div>
]}>
<Skeleton loading={fetching} active>
{local && (
<Collapse defaultActiveKey={['0']} className={styles.collapse} style={{marginBottom: 24}}>
<Collapse.Panel header={(
<div className={styles.header}>
<b className={styles.title}/>
<Steps size="small" className={styles.step} current={local.step} status={local.status}>
<StepItem title="构建准备" item={local} step={0}/>
<StepItem title="检出前任务" item={local} step={1}/>
<StepItem title="执行检出" item={local} step={2}/>
<StepItem title="检出后任务" item={local} step={3}/>
<StepItem title="执行打包" item={local} step={4}/>
</Steps>
</div>
)}>
<OutView setTerm={term => handleSetTerm(term, 'local')}/>
</Collapse.Panel>
</Collapse>
)}
<Collapse
defaultActiveKey="0"
className={styles.collapse}
expandIcon={({isActive}) => <CaretRightOutlined style={{fontSize: 16}} rotate={isActive ? 90 : 0}/>}>
{Object.entries(outputs).map(([key, item], index) => (
{Object.entries(hosts).map(([key, item], index) => (
<Collapse.Panel
key={index}
header={