A 灰度发布

This commit is contained in:
张臻 2020-11-07 17:37:26 +08:00
parent d6f92b9136
commit da796cba8c
8 changed files with 199 additions and 63 deletions

View File

@ -196,6 +196,61 @@ func (Deploy) Publish(gp *core.Goploy) *core.Response {
return &core.Response{}
}
// GreyPublish the project
func (Deploy) GreyPublish(gp *core.Goploy) *core.Response {
type ReqData struct {
ProjectID int64 `json:"projectId" validate:"gt=0"`
Commit string `json:"commit"`
ServerIDs []int64 `json:"serverIds"`
}
var reqData ReqData
if err := verify(gp.Body, &reqData); err != nil {
return &core.Response{Code: core.Error, Message: err.Error()}
}
var err error
project, err := model.Project{ID: reqData.ProjectID}.GetData()
if err != nil {
return &core.Response{Code: core.Error, Message: err.Error()}
}
if project.DeployState == model.ProjectDeploying {
return &core.Response{Code: core.Error, Message: "project is being build"}
}
bindProjectServers, err := model.ProjectServer{ProjectID: project.ID}.GetBindServerListByProjectID()
if err != nil {
return &core.Response{Code: core.Error, Message: err.Error()}
}
projectServers := model.ProjectServers{}
for _, projectServer := range bindProjectServers {
for _, serverID := range reqData.ServerIDs {
if projectServer.ServerID == serverID {
projectServers = append(projectServers, projectServer)
}
}
}
project.PublisherID = gp.UserInfo.ID
project.PublisherName = gp.UserInfo.Name
project.DeployState = model.ProjectDeploying
project.LastPublishToken = uuid.New().String()
err = project.Publish()
if err != nil {
return &core.Response{Code: core.Error, Message: err.Error()}
}
go service.Sync{
UserInfo: gp.UserInfo,
Project: project,
ProjectServers: projectServers,
CommitID: reqData.Commit,
}.Exec()
return &core.Response{}
}
func (Deploy) Review(gp *core.Goploy) *core.Response {
type ReqData struct {
ProjectReviewID int64 `json:"projectReviewId" validate:"gt=0"`

View File

@ -80,6 +80,7 @@ func Init() *router.Router {
rt.Add("/deploy/getPreview", http.MethodGet, controller.Deploy{}.GetPreview)
rt.Add("/deploy/review", http.MethodPost, controller.Deploy{}.Review).Roles([]string{core.RoleAdmin, core.RoleManager, core.RoleGroupManager})
rt.Add("/deploy/publish", http.MethodPost, controller.Deploy{}.Publish, middleware.HasPublishAuth)
rt.Add("/deploy/greyPublish", http.MethodPost, controller.Deploy{}.GreyPublish, middleware.HasPublishAuth).Roles([]string{core.RoleAdmin, core.RoleManager, core.RoleGroupManager})
rt.Add("/deploy/webhook", http.MethodPost, controller.Deploy{}.Webhook, middleware.FilterEvent)
rt.Add("/deploy/callback", http.MethodGet, controller.Deploy{}.Callback)

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
import request from '@/utils/request'
/**
* @param {string} projectName
* @return {Promise}
*/
export function getList(projectName) {
@ -12,7 +13,7 @@ export function getList(projectName) {
}
/**
* @param {int} id
* @param {string} lastPublishToken
* @return {Promise}
*/
export function getDetail(lastPublishToken) {
@ -26,7 +27,8 @@ export function getDetail(lastPublishToken) {
}
/**
* @param {int} id
* @param {object} pagination
* @param {object} params
* @return {Promise}
*/
export function getPreview({ page, rows }, params) {
@ -66,7 +68,8 @@ export function getTagList(id) {
}
/**
* @param {int} id
* @param {int} projectId
* @param {string} commit
* @return {Promise}
*/
export function publish(projectId, commit) {
@ -78,7 +81,22 @@ export function publish(projectId, commit) {
}
/**
* @param {int} id
* @param {int} projectId
* @param {string} commit
* @param {Array} serverIds
* @return {Promise}
*/
export function greyPublish(projectId, commit, serverIds) {
return request({
url: '/deploy/greyPublish',
method: 'post',
data: { projectId, commit, serverIds }
})
}
/**
* @param {int} projectReviewId
* @param {int} state
* @return {Promise}
*/
export function review(projectReviewId, state) {
@ -88,15 +106,3 @@ export function review(projectReviewId, state) {
data: { projectReviewId, state }
})
}
/**
* @param {int} id
* @return {Promise}
*/
export function rollback(projectId, commit) {
return request({
url: '/deploy/rollback',
method: 'post',
data: { projectId, commit }
})
}

View File

@ -28,6 +28,7 @@ export default {
func: 'Func',
param: 'Param',
deploy: 'Deploy',
grey: 'Grey',
initial: 'Initial',
search: 'Search',
tips: 'Tips',
@ -180,6 +181,6 @@ export default {
reviewTips: 'This action will approve commit, continue?',
reviewStateOption: ['Wait', 'Reviewed', 'Rejected'],
removeProjectTaskTips: 'This action will delete the crontab task in {projectName}, continue?',
rollbackTips: 'This action will rebuild {commit}, continue?'
publishCommitTips: 'This action will rebuild {commit}, continue?'
}
}

View File

@ -28,6 +28,7 @@ export default {
func: '功能',
param: '参数',
deploy: '构建',
grey: '灰度',
initial: '初始化',
search: '搜索',
tips: '提示',
@ -191,6 +192,6 @@ export default {
reviewTips: '此操作将通过该次提交, 是否继续?',
reviewStateOption: ['待审', '已审', '拒审'],
removeProjectTaskTips: '此操作删除{projectName}的定时任务, 是否继续?',
rollbackTips: '此操作将重新构建{commit}, 是否继续?'
publishCommitTips: '此操作将重新构建{commit}, 是否继续?'
}
}

View File

@ -134,7 +134,7 @@
<!-- <span v-if="item.publishState === 1" style="color:#67C23A;float:right;">{{ $t('success') }}</span>
<span v-else style="color:#F56C6C;float:right;">{{ $t('fail') }}</span> -->
</el-radio>
<el-button type="danger" plain @click="rollback(item)">rebuild</el-button>
<el-button type="danger" plain @click="publishByCommit(item)">rebuild</el-button>
</el-row>
</el-row>
</el-radio-group>
@ -255,9 +255,10 @@
{{ parseTime(scope.row.timestamp) }}
</template>
</el-table-column>
<el-table-column prop="operation" :label="$t('op')" width="80" align="center" fixed="right">
<el-table-column prop="operation" :label="$t('op')" width="160" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="danger" @click="rollback(scope.row)">{{ $t('deploy') }}</el-button>
<el-button type="danger" @click="publishByCommit(scope.row)">{{ $t('deploy') }}</el-button>
<el-button v-if="!isMember()" type="warning" @click="handleGreyPublish(scope.row)">{{ $t('grey') }}</el-button>
</template>
</el-table-column>
</el-table>
@ -312,9 +313,10 @@
{{ parseTime(scope.row.timestamp) }}
</template>
</el-table-column>
<el-table-column prop="operation" :label="$t('op')" width="80" align="center" fixed="right">
<el-table-column prop="operation" :label="$t('op')" width="160" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="danger" @click="rollback(scope.row)">{{ $t('deploy') }}</el-button>
<el-button type="danger" @click="publishByCommit(scope.row)">{{ $t('deploy') }}</el-button>
<el-button v-if="!isMember()" type="warning" @click="handleGreyPublish(scope.row)">{{ $t('grey') }}</el-button>
</template>
</el-table-column>
</el-table>
@ -322,6 +324,25 @@
<el-button @click="tagDialogVisible = false">{{ $t('cancel') }}</el-button>
</div>
</el-dialog>
<el-dialog :title="$t('deploy')" :visible.sync="greyServerDialogVisible">
<el-form ref="greyServerForm" :rules="greyServerFormRules" :model="greyServerFormData">
<el-form-item :label="$t('server')" label-width="80px" prop="serverIds">
<el-checkbox-group v-model="greyServerFormData.serverIds">
<el-checkbox
v-for="(item, index) in greyServerFormProps.serverOption"
:key="index"
:label="item.serverId"
>
{{ item.serverName+ '(' + item.serverDescription + ')' }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="greyServerDialogVisible = false">{{ $t('cancel') }}</el-button>
<el-button :disabled="greyServerFormProps.disabled" type="primary" @click="greyPublish">{{ $t('confirm') }}</el-button>
</div>
</el-dialog>
<el-dialog :title="$t('manage')" :visible.sync="taskListDialogVisible">
<el-row class="app-bar" type="flex" justify="end">
<el-button type="primary" icon="el-icon-plus" @click="handleAddProjectTask(selectedItem)" />
@ -473,8 +494,8 @@
</template>
<script>
import tableHeight from '@/mixin/tableHeight'
import { getList, getDetail, getPreview, getCommitList, getTagList, publish, review } from '@/api/deploy'
import { addTask, editTask, removeTask, getTaskList, getReviewList } from '@/api/project'
import { getList, getDetail, getPreview, getCommitList, getTagList, publish, review, greyPublish } from '@/api/deploy'
import { addTask, editTask, removeTask, getTaskList, getBindServerList, getReviewList } from '@/api/project'
import { getUserOption } from '@/api/namespace'
import { parseTime, parseGitURL } from '@/utils'
@ -489,6 +510,7 @@ export default {
publishToken: '',
commitDialogVisible: false,
tagDialogVisible: false,
greyServerDialogVisible: false,
taskDialogVisible: false,
taskListDialogVisible: false,
reviewDialogVisible: false,
@ -556,6 +578,20 @@ export default {
commitTableData: [],
tagTableLoading: false,
tagTableData: [],
greyServerFormProps: {
disabled: false,
serverOption: []
},
greyServerFormData: {
projectId: 0,
commit: 0,
serverIds: []
},
greyServerFormRules: {
serverIds: [
{ type: 'array', required: true, message: 'Server required', trigger: 'change' }
]
},
publishTraceList: [],
publishLocalTraceList: [],
publishRemoteTraceList: {},
@ -667,37 +703,6 @@ export default {
this.getList()
},
publish(data) {
const id = data.id
const h = this.$createElement
let color = ''
if (data.environment === 1) {
color = 'color: #F56C6C'
} else if (data.environment === 3) {
color = 'color: #E6A23C'
} else {
color = 'color: #909399'
}
this.$confirm('', this.$i18n.t('tips'), {
message: h('p', null, [
h('span', null, 'Deploy Project: '),
h('b', { style: color }, data.name + ' - ' + this.$i18n.t(`envOption[${data.environment}]`))
]),
confirmButtonText: this.$i18n.t('confirm'),
cancelButtonText: this.$i18n.t('cancel'),
type: 'warning'
}).then(() => {
this.gitLog = []
this.remoteLog = {}
publish(id, '').then((response) => {
const projectIndex = this.tableData.findIndex(element => element.id === id)
this.tableData[projectIndex].deployState = 1
})
}).catch(() => {
this.$message.info('Cancel')
})
},
getDetail() {
getDetail(this.publishToken).then((response) => {
const publishTraceList = response.data.publishTraceList || []
@ -765,6 +770,16 @@ export default {
this.getDetail()
},
handleGreyPublish(data) {
getBindServerList(data.projectId).then((response) => {
this.greyServerFormProps.serverOption = response.data.list
})
// projectID
this.greyServerFormData.projectId = data.projectId
this.greyServerFormData.commit = data.commit
this.greyServerDialogVisible = true
},
getCommitList(data) {
const id = data.id
this.commitDialogVisible = true
@ -953,8 +968,39 @@ export default {
}
},
rollback(data) {
this.$confirm(this.$i18n.t('deployPage.rollbackTips', { commit: data.commit }), this.$i18n.t('tips'), {
publish(data) {
const id = data.id
const h = this.$createElement
let color = ''
if (data.environment === 1) {
color = 'color: #F56C6C'
} else if (data.environment === 3) {
color = 'color: #E6A23C'
} else {
color = 'color: #909399'
}
this.$confirm('', this.$i18n.t('tips'), {
message: h('p', null, [
h('span', null, 'Deploy Project: '),
h('b', { style: color }, data.name + ' - ' + this.$i18n.t(`envOption[${data.environment}]`))
]),
confirmButtonText: this.$i18n.t('confirm'),
cancelButtonText: this.$i18n.t('cancel'),
type: 'warning'
}).then(() => {
this.gitLog = []
this.remoteLog = {}
publish(id, '').then((response) => {
const projectIndex = this.tableData.findIndex(element => element.id === id)
this.tableData[projectIndex].deployState = 1
})
}).catch(() => {
this.$message.info('Cancel')
})
},
publishByCommit(data) {
this.$confirm(this.$i18n.t('deployPage.publishCommitTips', { commit: data.commit }), this.$i18n.t('tips'), {
confirmButtonText: this.$i18n.t('confirm'),
cancelButtonText: this.$i18n.t('cancel'),
type: 'warning'
@ -963,6 +1009,7 @@ export default {
const projectIndex = this.tableData.findIndex(element => element.id === data.projectId)
this.tableData[projectIndex].deployState = 1
this.commitDialogVisible = false
this.tagDialogVisible = false
this.dialogVisible = false
})
}).catch(() => {
@ -970,6 +1017,31 @@ export default {
})
},
greyPublish() {
this.$refs.greyServerForm.validate((valid) => {
if (valid) {
const data = this.greyServerFormData
this.$confirm(this.$i18n.t('deployPage.publishCommitTips', { commit: data.commit }), this.$i18n.t('tips'), {
confirmButtonText: this.$i18n.t('confirm'),
cancelButtonText: this.$i18n.t('cancel'),
type: 'warning'
}).then(() => {
greyPublish(data.projectId, data.commit, data.serverIds).then((response) => {
const projectIndex = this.tableData.findIndex(element => element.id === data.projectId)
this.tableData[projectIndex].deployState = 1
this.commitDialogVisible = false
this.tagDialogVisible = false
this.greyServerDialogVisible = false
})
}).catch(() => {
this.$message.info('Cancel')
})
} else {
return false
}
})
},
formatDetail(detail) {
return detail ? detail.replace(/\n|(\r\n)/g, '<br>') : ''
}

View File

@ -345,8 +345,8 @@
</el-dialog>
<el-dialog :title="$t('add')" :visible.sync="dialogAddServerVisible">
<el-form ref="addServerForm" :rules="addServerFormRules" :model="addServerFormData">
<el-form-item :label="$t('server')" label-width="120px" prop="serverIds">
<el-select v-model="addServerFormData.serverIds" multiple>
<el-form-item :label="$t('server')" label-width="80px" prop="serverIds">
<el-select v-model="addServerFormData.serverIds" multiple style="width: 100%">
<el-option
v-for="(item, index) in serverOption"
:key="index"
@ -363,8 +363,8 @@
</el-dialog>
<el-dialog :title="$t('add')" :visible.sync="dialogAddUserVisible">
<el-form ref="addUserForm" :rules="addUserFormRules" :model="addUserFormData">
<el-form-item :label="$t('member')" label-width="120px" prop="userIds">
<el-select v-model="addUserFormData.userIds" multiple>
<el-form-item :label="$t('member')" label-width="80px" prop="userIds">
<el-select v-model="addUserFormData.userIds" multiple style="width: 100%">
<el-option
v-for="(item, index) in userOption.filter(item => [$global.Admin, $global.Manager].indexOf(item.role) === -1)"
:key="index"