A 自定义发布支持发布时上传数据 #156

pull/172/head
vapao 2020-08-09 22:25:21 +08:00
parent 708ed4119c
commit c82906a5df
5 changed files with 115 additions and 36 deletions

View File

@ -7,5 +7,6 @@ from .views import *
urlpatterns = [ urlpatterns = [
path('request/', RequestView.as_view()), path('request/', RequestView.as_view()),
path('request/upload/', do_upload),
path('request/<int:r_id>/', RequestDetailView.as_view()), path('request/<int:r_id>/', RequestDetailView.as_view()),
] ]

View File

@ -136,7 +136,9 @@ def _ext2_deploy(req, helper, env):
tmp_transfer_file = None tmp_transfer_file = None
for action in host_actions: for action in host_actions:
if action.get('type') == 'transfer': if action.get('type') == 'transfer':
helper.send_info('local', f'{human_time()} 检测到数据传输动作,执行打包... ') if action.get('src_mode') == '1':
break
helper.send_info('local', f'{human_time()} 检测到来源为本地路径的数据传输动作,执行打包... ')
action['src'] = action['src'].rstrip('/ ') action['src'] = action['src'].rstrip('/ ')
action['dst'] = action['dst'].rstrip('/ ') action['dst'] = action['dst'].rstrip('/ ')
if not action['src'] or not action['dst']: if not action['src'] or not action['dst']:
@ -242,15 +244,23 @@ def _deploy_ext2_host(helper, h_id, actions, env):
for index, action in enumerate(actions): for index, action in enumerate(actions):
helper.send_step(h_id, 2 + index, f'{human_time()} {action["title"]}...\r\n') helper.send_step(h_id, 2 + index, f'{human_time()} {action["title"]}...\r\n')
if action.get('type') == 'transfer': if action.get('type') == 'transfer':
sp_dir, sd_dst = os.path.split(action['src']) if action.get('src_mode') == '1':
tar_gz_file = f'{env.SPUG_VERSION}.tar.gz' try:
try: ssh.put_file(os.path.join(REPOS_DIR, env.SPUG_DEPLOY_ID, env.SPUG_VERSION), action['dst'])
ssh.put_file(os.path.join(sp_dir, tar_gz_file), f'/tmp/{tar_gz_file}') except Exception as e:
except Exception as e: helper.send_error(host.id, f'exception: {e}')
helper.send_error(host.id, f'exception: {e}') helper.send_info(host.id, 'transfer completed\r\n')
continue
else:
sp_dir, sd_dst = os.path.split(action['src'])
tar_gz_file = f'{env.SPUG_VERSION}.tar.gz'
try:
ssh.put_file(os.path.join(sp_dir, tar_gz_file), f'/tmp/{tar_gz_file}')
except Exception as e:
helper.send_error(host.id, f'exception: {e}')
command = f'cd /tmp && tar xf {tar_gz_file} && rm -f {tar_gz_file} ' command = f'cd /tmp && tar xf {tar_gz_file} && rm -f {tar_gz_file} '
command += f'&& rm -rf {action["dst"]} && mv /tmp/{sd_dst} {action["dst"]} && echo "transfer completed"' command += f'&& rm -rf {action["dst"]} && mv /tmp/{sd_dst} {action["dst"]} && echo "transfer completed"'
else: else:
command = f'cd /tmp && {action["data"]}' command = f'cd /tmp && {action["data"]}'
helper.remote(host.id, ssh, command, env) helper.remote(host.id, ssh, command, env)

View File

@ -4,17 +4,20 @@
from django.views.generic import View from django.views.generic import View
from django.db.models import F from django.db.models import F
from django.conf import settings from django.conf import settings
from django.http.response import HttpResponseBadRequest
from django_redis import get_redis_connection from django_redis import get_redis_connection
from libs import json_response, JsonParser, Argument, human_datetime, human_time from libs import json_response, JsonParser, Argument, human_datetime, human_time
from apps.deploy.models import DeployRequest from apps.deploy.models import DeployRequest
from apps.app.models import Deploy from apps.app.models import Deploy, DeployExtend2
from apps.deploy.utils import deploy_dispatch, Helper from apps.deploy.utils import deploy_dispatch, Helper
from apps.host.models import Host from apps.host.models import Host
from collections import defaultdict from collections import defaultdict
from threading import Thread from threading import Thread
from datetime import datetime from datetime import datetime
import subprocess
import json import json
import uuid import uuid
import os
class RequestView(View): class RequestView(View):
@ -63,6 +66,11 @@ class RequestView(View):
return json_response(error='请选择要发布的Tag') return json_response(error='请选择要发布的Tag')
if form.extra[0] == 'branch' and not form.extra[2]: if form.extra[0] == 'branch' and not form.extra[2]:
return json_response(error='请选择要发布的分支及Commit ID') return json_response(error='请选择要发布的分支及Commit ID')
if deploy.extend == '2':
if DeployExtend2.objects.filter(host_actions__contains='"src_mode": "1"').exists():
if len(form.extra) < 2:
return json_response(error='该应用的发布配置中使用了数据传输动作且设置为发布时上传,请上传要传输的数据')
form.version = form.extra[1].get('path')
form.status = '0' if deploy.is_audit else '1' form.status = '0' if deploy.is_audit else '1'
form.extra = json.dumps(form.extra) form.extra = json.dumps(form.extra)
form.host_ids = json.dumps(form.host_ids) form.host_ids = json.dumps(form.host_ids)
@ -215,3 +223,22 @@ class RequestDetailView(View):
req.save() req.save()
Thread(target=Helper.send_deploy_notify, args=(req, 'approve_rst')).start() Thread(target=Helper.send_deploy_notify, args=(req, 'approve_rst')).start()
return json_response(error=error) return json_response(error=error)
def do_upload(request):
repos_dir = settings.REPOS_DIR
file = request.FILES['file']
deploy_id = request.POST.get('deploy_id')
if file and deploy_id:
dir_name = os.path.join(repos_dir, deploy_id)
file_name = datetime.now().strftime("%Y%m%d%H%M%S")
command = f'mkdir -p {dir_name} && cd {dir_name} && ls | sort -rn | tail -n +11 | xargs rm -rf'
code, outputs = subprocess.getstatusoutput(command)
if code != 0:
return json_response(error=outputs)
with open(os.path.join(dir_name, file_name), 'wb') as f:
for chunk in file.chunks():
f.write(chunk)
return json_response(file_name)
else:
return HttpResponseBadRequest()

View File

@ -33,7 +33,7 @@ class Ext2Setup3 extends React.Component {
const info = store.deploy; const info = store.deploy;
info['app_id'] = store.app_id; info['app_id'] = store.app_id;
info['extend'] = '2'; info['extend'] = '2';
info['host_actions'] = info['host_actions'].filter(x => (x.title && x.data) || (x.title && x.src && x.dst)); info['host_actions'] = info['host_actions'].filter(x => (x.title && x.data) || (x.title && (x.src || x.src_mode === '1') && x.dst));
info['server_actions'] = info['server_actions'].filter(x => x.title && x.data); info['server_actions'] = info['server_actions'].filter(x => x.title && x.data);
http.post('/api/app/deploy/', info) http.post('/api/app/deploy/', info)
.then(res => { .then(res => {
@ -101,31 +101,42 @@ class Ext2Setup3 extends React.Component {
placeholder="请输入"/> placeholder="请输入"/>
</Form.Item> </Form.Item>
{item['type'] === 'transfer' ? ([ {item['type'] === 'transfer' ? ([
<Form.Item key={0} label="过滤规则" help={this.helpMap[item['mode']]}> <Form.Item key={0} required label="数据来源">
<Input <Input
spellCheck={false} spellCheck={false}
placeholder="请输入逗号分割的过滤规则" disabled={store.isReadOnly || item['src_mode'] === '1'}
value={item['rule']} placeholder="请输入本地部署spug的容器或主机路径"
onChange={e => item['rule'] = e.target.value.replace('', ',')} value={item['src']}
disabled={store.isReadOnly || item['mode'] === '0'} onChange={e => item['src'] = e.target.value}
addonBefore={( addonBefore={(
<Select disabled={store.isReadOnly} style={{width: 100}} value={item['mode']} <Select disabled={store.isReadOnly} style={{width: 120}} value={item['src_mode'] || '0'}
onChange={v => item['mode'] = v}> onChange={v => item['src_mode'] = v}>
<Select.Option value="0">关闭</Select.Option> <Select.Option value="0">本地路径</Select.Option>
<Select.Option value="1">包含</Select.Option> <Select.Option value="1">发布时上传</Select.Option>
<Select.Option value="2">排除</Select.Option>
</Select> </Select>
)}/> )}/>
</Form.Item>, </Form.Item>,
<Form.Item key={1} required label="传输路径" extra={<a item['src_mode'] === '0' ? (
<Form.Item key={1} label="过滤规则" help={this.helpMap[item['mode']]}>
<Input
spellCheck={false}
placeholder="请输入逗号分割的过滤规则"
value={item['rule']}
onChange={e => item['rule'] = e.target.value.replace('', ',')}
disabled={store.isReadOnly || item['mode'] === '0'}
addonBefore={(
<Select disabled={store.isReadOnly} style={{width: 120}} value={item['mode']}
onChange={v => item['mode'] = v}>
<Select.Option value="0">关闭</Select.Option>
<Select.Option value="1">包含</Select.Option>
<Select.Option value="2">排除</Select.Option>
</Select>
)}/>
</Form.Item>
) : null,
<Form.Item key={2} required label="目标路径" extra={<a
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
href="https://spug.dev/docs/deploy-config#%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93">使用前请务必阅读官方文档</a>}> href="https://spug.dev/docs/deploy-config#%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93">使用前请务必阅读官方文档</a>}>
<Input
disabled={store.isReadOnly}
spellCheck={false}
value={item['src']}
placeholder="请输入本地路径部署spug的容器或主机"
onChange={e => item['src'] = e.target.value}/>
<Input <Input
disabled={store.isReadOnly} disabled={store.isReadOnly}
spellCheck={false} spellCheck={false}
@ -162,7 +173,7 @@ class Ext2Setup3 extends React.Component {
block block
type="dashed" type="dashed"
disabled={store.isReadOnly || lds.findIndex(host_actions, x => x.type === 'transfer') !== -1} disabled={store.isReadOnly || lds.findIndex(host_actions, x => x.type === 'transfer') !== -1}
onClick={() => host_actions.push({type: 'transfer', title: '数据传输', mode: '0'})}> onClick={() => host_actions.push({type: 'transfer', title: '数据传输', mode: '0', src_mode: '0'})}>
<Icon type="plus"/>添加数据传输动作仅能添加一个 <Icon type="plus"/>添加数据传输动作仅能添加一个
</Button> </Button>
</Form.Item> </Form.Item>

View File

@ -5,7 +5,7 @@
*/ */
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Modal, Form, Input, Tag, message } from 'antd'; import { Modal, Form, Input, Tag, Upload, message, Button, Icon } from 'antd';
import hostStore from 'pages/host/store'; import hostStore from 'pages/host/store';
import http from 'libs/http'; import http from 'libs/http';
import store from './store'; import store from './store';
@ -15,9 +15,11 @@ import lds from 'lodash';
class Ext2Form extends React.Component { class Ext2Form extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.token = localStorage.getItem('token');
this.state = { this.state = {
loading: false, loading: false,
type: null, uploading: false,
fileList: [],
host_ids: store.record['app_host_ids'].concat() host_ids: store.record['app_host_ids'].concat()
} }
} }
@ -26,6 +28,11 @@ class Ext2Form extends React.Component {
if (hostStore.records.length === 0) { if (hostStore.records.length === 0) {
hostStore.fetchRecords() hostStore.fetchRecords()
} }
const file = lds.get(store, 'record.extra.1');
if (file) {
file.uid = '0';
this.setState({fileList: [file]})
}
} }
handleSubmit = () => { handleSubmit = () => {
@ -37,6 +44,9 @@ class Ext2Form extends React.Component {
formData['id'] = store.record.id; formData['id'] = store.record.id;
formData['deploy_id'] = store.record.deploy_id; formData['deploy_id'] = store.record.deploy_id;
formData['extra'] = [formData['extra']]; formData['extra'] = [formData['extra']];
if (this.state.fileList.length > 0) {
formData['extra'].push(lds.pick(this.state.fileList[0], ['path', 'name']))
}
formData['host_ids'] = this.state.host_ids; formData['host_ids'] = this.state.host_ids;
http.post('/api/deploy/request/', formData) http.post('/api/deploy/request/', formData)
.then(res => { .then(res => {
@ -57,9 +67,28 @@ class Ext2Form extends React.Component {
} }
}; };
handleUploadChange = (v) => {
if (v.fileList.length === 0) {
this.setState({fileList: []})
} else {
this.setState({fileList: [v.file]})
}
};
handleUpload = (file, fileList) => {
this.setState({uploading: true});
const formData = new FormData();
formData.append('file', file);
formData.append('deploy_id', store.record.deploy_id);
http.post('/api/deploy/request/upload/', formData)
.then(res => file.path = res)
.finally(() => this.setState({uploading: false}))
return false
};
render() { render() {
const info = store.record; const info = store.record;
const {host_ids} = this.state; const {host_ids, fileList, uploading} = this.state;
const {getFieldDecorator} = this.props.form; const {getFieldDecorator} = this.props.form;
return ( return (
<Modal <Modal
@ -81,10 +110,11 @@ class Ext2Form extends React.Component {
<Input placeholder="请输入环境变量 SPUG_RELEASE 的值"/> <Input placeholder="请输入环境变量 SPUG_RELEASE 的值"/>
)} )}
</Form.Item> </Form.Item>
<Form.Item label="备注信息"> <Form.Item label="上传数据" help="通过数据传输动作来使用上传的文件。">
{getFieldDecorator('desc', {initialValue: info['desc']})( <Upload name="file" fileList={fileList} headers={{'X-Token': this.token}} beforeUpload={this.handleUpload}
<Input placeholder="请输入备注信息"/> data={{deploy_id: info.deploy_id}} onChange={this.handleUploadChange}>
)} {fileList.length === 0 ? <Button loading={uploading}><Icon type="upload"/> 点击上传</Button> : null}
</Upload>
</Form.Item> </Form.Item>
<Form.Item required label="发布目标主机" help="通过点击主机名称自由选择本次发布的主机。"> <Form.Item required label="发布目标主机" help="通过点击主机名称自由选择本次发布的主机。">
{info['app_host_ids'].map(id => ( {info['app_host_ids'].map(id => (