diff --git a/spug_api/apps/deploy/urls.py b/spug_api/apps/deploy/urls.py index 5c335236..c7e95eb4 100644 --- a/spug_api/apps/deploy/urls.py +++ b/spug_api/apps/deploy/urls.py @@ -7,5 +7,6 @@ urlpatterns = [ path('request/', RequestView.as_view()), + path('request/upload/', do_upload), path('request//', RequestDetailView.as_view()), ] diff --git a/spug_api/apps/deploy/utils.py b/spug_api/apps/deploy/utils.py index bbf2c877..8fbf09ca 100644 --- a/spug_api/apps/deploy/utils.py +++ b/spug_api/apps/deploy/utils.py @@ -136,7 +136,9 @@ def _ext2_deploy(req, helper, env): tmp_transfer_file = None for action in host_actions: 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['dst'] = action['dst'].rstrip('/ ') 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): helper.send_step(h_id, 2 + index, f'{human_time()} {action["title"]}...\r\n') if action.get('type') == 'transfer': - 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'&& rm -rf {action["dst"]} && mv /tmp/{sd_dst} {action["dst"]} && echo "transfer completed"' + if action.get('src_mode') == '1': + try: + ssh.put_file(os.path.join(REPOS_DIR, env.SPUG_DEPLOY_ID, env.SPUG_VERSION), action['dst']) + except Exception as 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'&& rm -rf {action["dst"]} && mv /tmp/{sd_dst} {action["dst"]} && echo "transfer completed"' else: command = f'cd /tmp && {action["data"]}' helper.remote(host.id, ssh, command, env) diff --git a/spug_api/apps/deploy/views.py b/spug_api/apps/deploy/views.py index 6b3abca3..605165a0 100644 --- a/spug_api/apps/deploy/views.py +++ b/spug_api/apps/deploy/views.py @@ -4,17 +4,20 @@ from django.views.generic import View from django.db.models import F from django.conf import settings +from django.http.response import HttpResponseBadRequest from django_redis import get_redis_connection from libs import json_response, JsonParser, Argument, human_datetime, human_time 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.host.models import Host from collections import defaultdict from threading import Thread from datetime import datetime +import subprocess import json import uuid +import os class RequestView(View): @@ -63,6 +66,11 @@ def post(self, request): return json_response(error='请选择要发布的Tag') if form.extra[0] == 'branch' and not form.extra[2]: 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.extra = json.dumps(form.extra) form.host_ids = json.dumps(form.host_ids) @@ -215,3 +223,22 @@ def patch(self, request, r_id): req.save() Thread(target=Helper.send_deploy_notify, args=(req, 'approve_rst')).start() 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() diff --git a/spug_web/src/pages/deploy/app/Ext2Setup3.js b/spug_web/src/pages/deploy/app/Ext2Setup3.js index 2eadd194..0b824084 100644 --- a/spug_web/src/pages/deploy/app/Ext2Setup3.js +++ b/spug_web/src/pages/deploy/app/Ext2Setup3.js @@ -33,7 +33,7 @@ class Ext2Setup3 extends React.Component { const info = store.deploy; info['app_id'] = store.app_id; 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); http.post('/api/app/deploy/', info) .then(res => { @@ -101,31 +101,42 @@ class Ext2Setup3 extends React.Component { placeholder="请输入"/> {item['type'] === 'transfer' ? ([ - + item['rule'] = e.target.value.replace(',', ',')} - disabled={store.isReadOnly || item['mode'] === '0'} + disabled={store.isReadOnly || item['src_mode'] === '1'} + placeholder="请输入本地(部署spug的容器或主机)路径" + value={item['src']} + onChange={e => item['src'] = e.target.value} addonBefore={( - item['src_mode'] = v}> + 本地路径 + 发布时上传 )}/> , - + item['rule'] = e.target.value.replace(',', ',')} + disabled={store.isReadOnly || item['mode'] === '0'} + addonBefore={( + + )}/> + + ) : null, + 使用前请务必阅读官方文档。}> - item['src'] = e.target.value}/> 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'})}> 添加数据传输动作(仅能添加一个) diff --git a/spug_web/src/pages/deploy/request/Ext2Form.js b/spug_web/src/pages/deploy/request/Ext2Form.js index a04a9ed4..43af5546 100644 --- a/spug_web/src/pages/deploy/request/Ext2Form.js +++ b/spug_web/src/pages/deploy/request/Ext2Form.js @@ -5,7 +5,7 @@ */ import React from '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 http from 'libs/http'; import store from './store'; @@ -15,9 +15,11 @@ import lds from 'lodash'; class Ext2Form extends React.Component { constructor(props) { super(props); + this.token = localStorage.getItem('token'); this.state = { loading: false, - type: null, + uploading: false, + fileList: [], host_ids: store.record['app_host_ids'].concat() } } @@ -26,6 +28,11 @@ class Ext2Form extends React.Component { if (hostStore.records.length === 0) { hostStore.fetchRecords() } + const file = lds.get(store, 'record.extra.1'); + if (file) { + file.uid = '0'; + this.setState({fileList: [file]}) + } } handleSubmit = () => { @@ -37,6 +44,9 @@ class Ext2Form extends React.Component { formData['id'] = store.record.id; formData['deploy_id'] = store.record.deploy_id; 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; http.post('/api/deploy/request/', formData) .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() { const info = store.record; - const {host_ids} = this.state; + const {host_ids, fileList, uploading} = this.state; const {getFieldDecorator} = this.props.form; return ( )} - - {getFieldDecorator('desc', {initialValue: info['desc']})( - - )} + + + {fileList.length === 0 ? : null} + {info['app_host_ids'].map(id => (