From 20f28fd64ce9f020c98227e6b4996b7ea44e5048 Mon Sep 17 00:00:00 2001 From: vapao Date: Tue, 27 Apr 2021 23:30:41 +0800 Subject: [PATCH] add webhook for auto deploy --- spug_api/apps/apis/deploy.py | 96 ++++++++++++++++ spug_api/apps/apis/urls.py | 2 + spug_api/apps/app/utils.py | 12 +- spug_api/apps/deploy/models.py | 3 +- spug_api/apps/repository/utils.py | 4 +- spug_api/libs/gitlib.py | 4 +- spug_api/libs/middleware.py | 3 +- spug_web/src/pages/deploy/app/AutoDeploy.js | 105 ++++++++++++++++++ spug_web/src/pages/deploy/app/Table.js | 13 ++- spug_web/src/pages/deploy/app/index.js | 2 + .../src/pages/deploy/app/index.module.css | 5 + spug_web/src/pages/deploy/app/store.js | 6 + spug_web/src/pages/deploy/request/Table.js | 1 + 13 files changed, 241 insertions(+), 15 deletions(-) create mode 100644 spug_api/apps/apis/deploy.py create mode 100644 spug_web/src/pages/deploy/app/AutoDeploy.js diff --git a/spug_api/apps/apis/deploy.py b/spug_api/apps/apis/deploy.py new file mode 100644 index 0000000..6f4aa6b --- /dev/null +++ b/spug_api/apps/apis/deploy.py @@ -0,0 +1,96 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Copyright: (c) +# Released under the AGPL-3.0 License. +from django.http.response import HttpResponseBadRequest, HttpResponseForbidden, HttpResponse +from apps.setting.utils import AppSetting +from apps.deploy.models import Deploy, DeployRequest +from apps.repository.models import Repository +from apps.repository.utils import dispatch as build_dispatch +from apps.deploy.utils import dispatch as deploy_dispatch +from threading import Thread +import json + + +def auto_deploy(request, deploy_id, kind): + token = request.headers.get('X-Gitlab-Token') or request.headers.get('X-Gitee-Token') + if token and _is_valid_token(token): + try: + body = json.loads(request.body) + commit_id = body['after'] + _, _kind, ref = body['ref'].split('/', 2) + if commit_id != '0000000000000000000000000000000000000000': + if kind == 'branch': + if _kind == 'heads' and ref == request.GET.get('name'): + Thread(target=_dispatch, args=(deploy_id, ref, commit_id)).start() + return HttpResponse(status=202) + elif kind == 'tag': + if _kind == 'tags': + Thread(target=_dispatch, args=(deploy_id, ref)).start() + return HttpResponse(status=202) + return HttpResponse(status=204) + except Exception as e: + return HttpResponseBadRequest(e) + return HttpResponseForbidden() + + +def _is_valid_token(token): + api_key = AppSetting.get_default('api_key') + return api_key == token + + +def _dispatch(deploy_id, ref, commit_id=None): + deploy = Deploy.objects.filter(pk=deploy_id).first() + if not deploy: + raise Exception(f'no such deploy id for {deploy_id}') + if deploy.extend == '1': + _deploy_extend_1(deploy, ref, commit_id) + else: + _deploy_extend_2(deploy, ref, commit_id) + + +def _deploy_extend_1(deploy, ref, commit_id=None): + if commit_id: + extra = ['branch', ref, commit_id] + version = f'#_b_{commit_id[:6]}' + else: + extra = ['tag', ref, None] + version = f'#_t_{ref}' + rep = Repository.objects.create( + deploy=deploy, + app_id=deploy.app_id, + env_id=deploy.env_id, + version=version, + status='1', + extra=json.dumps(extra), + spug_version=Repository.make_spug_version(deploy.id), + created_by=deploy.created_by) + rep = build_dispatch(rep) + if rep.status == '5': + req = DeployRequest.objects.create( + type='3', + status='2', + deploy=deploy, + repository=rep, + name=rep.version, + version=rep.version, + spug_version=rep.spug_version, + host_ids=deploy.host_ids, + created_by=deploy.created_by + ) + deploy_dispatch(req) + + +def _deploy_extend_2(deploy, ref, commit_id=None): + # 创建 环境变量 分支 commit-id tag + version = f'#_b_{commit_id[:6]}' if commit_id else f'#_t_{ref}' + req = DeployRequest.objects.create( + type='3', + status='2', + deploy=deploy, + name=version, + version=version, + spug_version=Repository.make_spug_version(deploy.id), + host_ids=deploy.host_ids, + created_by=deploy.created_by + ) + deploy_dispatch(req) diff --git a/spug_api/apps/apis/urls.py b/spug_api/apps/apis/urls.py index 9b8a086..2fe5da2 100644 --- a/spug_api/apps/apis/urls.py +++ b/spug_api/apps/apis/urls.py @@ -4,7 +4,9 @@ from django.urls import path from apps.apis import config +from apps.apis import deploy urlpatterns = [ path('config/', config.get_configs), + path('deploy///', deploy.auto_deploy) ] diff --git a/spug_api/apps/app/utils.py b/spug_api/apps/app/utils.py index 2a0c67c..be8bdd3 100644 --- a/spug_api/apps/app/utils.py +++ b/spug_api/apps/app/utils.py @@ -23,10 +23,14 @@ def parse_envs(text): def fetch_versions(deploy: Deploy): git_repo = deploy.extend_obj.git_repo repo_dir = os.path.join(settings.REPOS_DIR, str(deploy.id)) - try: - pkey = AppSetting.get('private_key') - except KeyError: - pkey = None + pkey = AppSetting.get_default('private_key') + with Git(git_repo, repo_dir, pkey) as git: + return git.fetch_branches_tags() + + +def fetch_repo(deploy_id, git_repo): + repo_dir = os.path.join(settings.REPOS_DIR, str(deploy_id)) + pkey = AppSetting.get_default('private_key') with Git(git_repo, repo_dir, pkey) as git: return git.fetch_branches_tags() diff --git a/spug_api/apps/deploy/models.py b/spug_api/apps/deploy/models.py index 219e341..b4b2f68 100644 --- a/spug_api/apps/deploy/models.py +++ b/spug_api/apps/deploy/models.py @@ -19,7 +19,8 @@ class DeployRequest(models.Model, ModelMixin): ) TYPES = ( ('1', '正常发布'), - ('2', '回滚') + ('2', '回滚'), + ('3', '自动发布'), ) deploy = models.ForeignKey(Deploy, on_delete=models.CASCADE) repository = models.ForeignKey(Repository, null=True, on_delete=models.SET_NULL) diff --git a/spug_api/apps/repository/utils.py b/spug_api/apps/repository/utils.py index 424a5a1..f3613fa 100644 --- a/spug_api/apps/repository/utils.py +++ b/spug_api/apps/repository/utils.py @@ -6,6 +6,7 @@ from django.conf import settings from django.db import close_old_connections from libs.utils import AttrDict, human_time from apps.repository.models import Repository +from apps.app.utils import fetch_repo import subprocess import json import uuid @@ -51,6 +52,7 @@ def dispatch(rep: Repository): rds.expire(rds_key, 14 * 24 * 60 * 60) rds.close() rep.save() + return rep def _build(rep: Repository, helper, env): @@ -66,6 +68,7 @@ def _build(rep: Repository, helper, env): else: 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') if extend.hook_pre_server: @@ -115,7 +118,6 @@ class Helper: return files def _send(self, message): - print(message) self.rds.rpush(self.key, json.dumps(message)) def send_info(self, key, message): diff --git a/spug_api/libs/gitlib.py b/spug_api/libs/gitlib.py index 04d358a..a2cdd0e 100644 --- a/spug_api/libs/gitlib.py +++ b/spug_api/libs/gitlib.py @@ -21,7 +21,7 @@ class Git: self.repo.archive(f, commit) def fetch_branches_tags(self): - self._fetch() + self.fetch() branches, tags = {}, {} for ref in self.repo.references: if isinstance(ref, RemoteReference): @@ -42,7 +42,7 @@ class Git: tags = sorted(tags.items(), key=lambda x: x[1]['date'], reverse=True) return branches, dict(tags) - def _fetch(self): + def fetch(self): try: self.repo.remotes.origin.fetch(p=True, P=True) except GitCommandError as e: diff --git a/spug_api/libs/middleware.py b/spug_api/libs/middleware.py index 336471d..391a192 100644 --- a/spug_api/libs/middleware.py +++ b/spug_api/libs/middleware.py @@ -35,8 +35,7 @@ class AuthenticationMiddleware(MiddlewareMixin): user = User.objects.filter(access_token=access_token).first() if user and x_real_ip == user.last_ip and user.token_expired >= time.time() and user.is_active: request.user = user - if request.path != '/notify/': - user.token_expired = time.time() + 8 * 60 * 60 + user.token_expired = time.time() + 8 * 60 * 60 user.save() return None response = json_response(error="验证失败,请重新登录") diff --git a/spug_web/src/pages/deploy/app/AutoDeploy.js b/spug_web/src/pages/deploy/app/AutoDeploy.js new file mode 100644 index 0000000..26c143b --- /dev/null +++ b/spug_web/src/pages/deploy/app/AutoDeploy.js @@ -0,0 +1,105 @@ +import React, { useState, useEffect } from 'react'; +import { observer } from 'mobx-react'; +import { Modal, Form, Input, Select, Radio, Button, message } from 'antd'; +import { LoadingOutlined, SyncOutlined } from '@ant-design/icons'; +import { http } from 'libs'; +import store from './store'; +import styles from './index.module.css'; + + +export default observer(function AutoDeploy() { + const [type, setType] = useState('branch'); + const [fetching, setFetching] = useState(false); + const [branches, setBranches] = useState([]); + const [branch, setBranch] = useState(); + const [url, setURL] = useState(); + + useEffect(() => { + if (store.deploy.extend === '1') { + fetchVersions() + } + }, []) + + useEffect(() => { + const prefix = window.location.origin; + let tmp = `${prefix}/api/apis/deploy/${store.deploy.id}/${type}/`; + if (type === 'branch') { + tmp += `?name=${branch}` + } + setURL(tmp) + }, [type, branch]) + + function fetchVersions() { + setFetching(true); + http.get(`/api/app/deploy/${store.deploy.id}/versions/`) + .then(res => setBranches(Object.keys(res.branches))) + .finally(() => setFetching(false)) + } + + function copyToClipBoard() { + const t = document.createElement('input'); + t.value = url; + document.body.appendChild(t); + t.select(); + document.execCommand('copy'); + t.remove(); + message.success('已复制') + } + + const tagMode = type === 'tag'; + return ( + store.autoVisible = false}> +
+ + setType(e.target.value)}> + Branch + Tag + + + {store.deploy.extend === '1' ? ( + + 根据你的网络情况,首次刷新可能会很慢,请耐心等待。 + 刷新失败? + }> + + + + + {fetching ? : + + } + + + ) : ( + + setBranch(e.target.value)} + placeholder="仅指定分支的事件触发自动发布"/> + + )} + {type === 'branch' && !branch ? ( + +
请指定分支名称。
+
+ ) : ( + +
{url}
+
+ )} +
+
+ ) +}) \ No newline at end of file diff --git a/spug_web/src/pages/deploy/app/Table.js b/spug_web/src/pages/deploy/app/Table.js index 4877081..2e801d5 100644 --- a/spug_web/src/pages/deploy/app/Table.js +++ b/spug_web/src/pages/deploy/app/Table.js @@ -102,10 +102,13 @@ function ComTable() { store.showExtForm(e, record.id, info, false, true)}>查看 - store.showExtForm(e, record.id, info)}>编辑 - handleDeployDelete(info)}>删除 + onClick={e => store.showAutoDeploy(info)}>Webhook + {hasPermission('deploy.app.edit') ? ( + store.showExtForm(e, record.id, info)}>编辑 + ) : hasPermission('deploy.app.config') ? ( + store.showExtForm(e, record.id, info, false, true)}>查看 + ) : null} + handleDeployDelete(info)}>删除 )}/> )} @@ -155,7 +158,7 @@ function ComTable() { store.showExtForm(e, info.id)}>新建发布 handleClone(e, info.id)}>克隆发布 store.showForm(e, info)}>编辑 - handleDelete(e, info)}>删除 + handleDelete(e, info)}>删除 )}/> )} diff --git a/spug_web/src/pages/deploy/app/index.js b/spug_web/src/pages/deploy/app/index.js index 9c0d193..9d9938a 100644 --- a/spug_web/src/pages/deploy/app/index.js +++ b/spug_web/src/pages/deploy/app/index.js @@ -12,6 +12,7 @@ import ComForm from './Form'; import Ext1Form from './Ext1Form'; import Ext2Form from './Ext2Form'; import AddSelect from './AddSelect'; +import AutoDeploy from './AutoDeploy'; import store from './store'; import envStore from 'pages/config/environment/store'; @@ -42,6 +43,7 @@ export default observer(function () { {store.addVisible && } {store.ext1Visible && } {store.ext2Visible && } + {store.autoVisible && } ); }) diff --git a/spug_web/src/pages/deploy/app/index.module.css b/spug_web/src/pages/deploy/app/index.module.css index cab68d1..e7093dc 100644 --- a/spug_web/src/pages/deploy/app/index.module.css +++ b/spug_web/src/pages/deploy/app/index.module.css @@ -71,4 +71,9 @@ right: 0; bottom: 0; z-index: 999; +} + +.webhook { + cursor: pointer; + color: #1890ff; } \ No newline at end of file diff --git a/spug_web/src/pages/deploy/app/store.js b/spug_web/src/pages/deploy/app/store.js index 96ce5e3..80d7d37 100644 --- a/spug_web/src/pages/deploy/app/store.js +++ b/spug_web/src/pages/deploy/app/store.js @@ -19,6 +19,7 @@ class Store { @observable addVisible = false; @observable ext1Visible = false; @observable ext2Visible = false; + @observable autoVisible = false; @observable selectorVisible = false; @observable f_name; @@ -79,6 +80,11 @@ class Store { } }; + showAutoDeploy = (deploy) => { + this.deploy = deploy; + this.autoVisible = true + } + addHost = () => { this.deploy['host_ids'].push(undefined) }; diff --git a/spug_web/src/pages/deploy/request/Table.js b/spug_web/src/pages/deploy/request/Table.js index 6b8667b..d33c3c6 100644 --- a/spug_web/src/pages/deploy/request/Table.js +++ b/spug_web/src/pages/deploy/request/Table.js @@ -18,6 +18,7 @@ function ComTable() { render: info => (
{info.type === '2' && R} + {info.type === '3' && A} {info.plan && P} {info.name}