開源端到端流水線實踐-需求與代碼管理
業(yè)務的簡稱為demo,微服務架構(gòu)。N多個微服務。服務命名:業(yè)務簡稱-應用名稱-類型(demo-hello-service)。特性分支開發(fā),版本分支發(fā)布。每個需求(任務/故事)對應一個特性分支。每個發(fā)布(release)對應一個版本分支。
1.需求與代碼管理
Jira作為需求和缺陷管理,采用Scrum開發(fā)方法,jira中的項目名稱與業(yè)務簡稱一致(demo)。Gitlab作為版本控制系統(tǒng),每個Group對應一個業(yè)務,每個微服務對應一個代碼庫。
需求與代碼關聯(lián):在jira中創(chuàng)建一個任務/故事,關聯(lián)模塊后自動在該模塊創(chuàng)建一個以ISSUE(任務/故事)ID的特性分支。此時的模塊等同于每個微服務的項目(代碼庫)名稱。以下面圖中為例:我們在demo項目中創(chuàng)建了一個模塊demo-hello-service,其實對應的就是Gitlab代碼庫中demo組的demo-hello-service服務。
特性分支:創(chuàng)建好每個模塊后,就可以實現(xiàn)需求與代碼關聯(lián)。例如:我們在Jira項目demo中創(chuàng)建一個問題,類型為故事(不受限制可為其他),重點是需要將改故事關聯(lián)到模塊(只有關聯(lián)到模塊,我們才能通過接口得知哪個問題關聯(lián)的哪個代碼庫)。
版本分支:當特性分支開發(fā)完成以及測試驗證完成后,基于主干分支創(chuàng)建一個版本分支,然后將所有的特性分支合并到版本分支。此時可以通過Jira中創(chuàng)建一個發(fā)布版本,然后問題關聯(lián)發(fā)布版本(此動作表示該特性分支已經(jīng)通過驗證,可以合并)。自動完成版本分支的創(chuàng)建和特性分支到版本分支的合并請求。
2. 配置過程
需求與代碼庫關聯(lián),主要用到的工具鏈為: Jira + GitLab + Jenkins。Jira負責創(chuàng)建需求,配置webhook。Jenkins負責接收Jira webhook請求,然后通過接口實現(xiàn)GitLab項目分支創(chuàng)建。
特性分支自動化:當我們在jira上面創(chuàng)建了問題,此時會通過Jira的webhook觸發(fā)對應的Jenkins作業(yè),該Jenkins作業(yè)通過解析Jira webhook傳遞的數(shù)據(jù),找到問題名稱和模塊名稱。調(diào)用GitlabAPI 項目查詢接口,根據(jù)模塊名稱找到代碼庫。調(diào)用GitLabAPI 分支創(chuàng)建接口,根據(jù)問題名稱基于主干分支創(chuàng)建一個特性分支。任務結(jié)束。
版本分支自動化:Jira創(chuàng)建發(fā)布版本,Issue關聯(lián)版本。自動在gitlab代碼庫基于master創(chuàng)建版本分支,并開啟特性分支到版本分支的合并請求。
2.1 準備工作
在Jenkins, 創(chuàng)建一個Pipeline 作業(yè)并配置GenericTrigger 觸發(fā)器,接收JiraWebhook數(shù)據(jù)。projectKey 參數(shù)表示Jira項目名稱,webHookData 參數(shù)為Jira webhook的所有數(shù)據(jù)。token 是觸發(fā)器的觸發(fā)token,這里默認采用的作業(yè)名稱(作業(yè)名稱要唯一)。
- triggers {
- GenericTrigger( causeString: 'Trigger By Jira Server -->>>>> Generic Cause',
- genericRequestVariables: [[key: 'projectKey', regexpFilter: '']],
- genericVariables: [[defaultValue: '', key: 'webHookData', regexpFilter: '', value: '$']],
- printContributedVariables: true,
- printPostContent: true,
- regexpFilterExpression: '',
- regexpFilterText: '',
- silentResponse: true,
- token: "${JOB_NAME}"
- )
在Jira項目中配置Webhook,勾選觸發(fā)事件填寫觸發(fā)URL。http://jenkins.idevops.site/generic-webhook-trigger/invoke?token=demo-jira-service&projectKey=${project.key} (這個地址是jenkins Generictrigger生成的,這里不做過多的介紹)
Jira webhook數(shù)據(jù)參考, 這些參數(shù)可以在Jenkinsfile中通過readJSON格式化,然后獲取值。
- response = readJSON text: """${webHookData}"""
- println(response)
- //獲取webhook的事件類型
- env.eventType = response["webhookEvent"]
- {
- "timestamp":1603087582648,
- "webhookEvent":"jira:issue_created",
- "issue_event_type_name":"issue_created",
- "user":Object{...},
- "issue":{
- "id":"10500",
- "self":"http://192.168.1.200:8050/rest/api/2/issue/10500",
- "key":"DEMO-2",
- "fields":{
- "issuetype":{
- "self":"http://192.168.1.200:8050/rest/api/2/issuetype/10001",
- "id":"10001",
- "description":"",
- "iconUrl":"http://192.168.1.200:8050/images/icons/issuetypes/story.svg",
- "name":"故事",
- "subtask":false
- },
- "components":[
- {
- "self":"http://192.168.1.200:8050/rest/api/2/component/10200",
- "id":"10200",
- "name":"demo-hello-service",
- "description":"demo-hello-service應用"
- }
- ],
- "timespent":null,
- "timeoriginalestimate":null,
- "description":null,
- ...
- ...
- ...
2.2 封裝GitLab接口
Gitlab接口文檔:https://docs.gitlab.com/ce/api/README.html
共享庫:src/org/devops/gitlab.groovy
- package org.devops
- //封裝HTTP請求
- def HttpReq(reqType,reqUrl,reqBody){
- def gitServer = "http://gitlab.idevops.site/api/v4"
- withCredentials([string(credentialsId: 'gitlab-token', variable: 'gitlabToken')]) {
- result = httpRequest customHeaders: [[maskValue: true, name: 'PRIVATE-TOKEN', value: "${gitlabToken}"]],
- httpMode: reqType,
- contentType: "APPLICATION_JSON",
- consoleLogResponseBody: true,
- ignoreSslErrors: true,
- requestBody: reqBody,
- url: "${gitServer}/${reqUrl}"
- //quiet: true
- }
- return result
- }
- //更新文件內(nèi)容
- def UpdateRepoFile(projectId,filePath,fileContent){
- apiUrl = "projects/${projectId}/repository/files/${filePath}"
- reqBody = """{"branch": "master","encoding":"base64", "content": "${fileContent}", "commit_message": "update a new file"}"""
- response = HttpReq('PUT',apiUrl,reqBody)
- println(response)
- }
- //獲取文件內(nèi)容
- def GetRepoFile(projectId,filePath){
- apiUrl = "projects/${projectId}/repository/files/${filePath}/raw?ref=master"
- response = HttpReq('GET',apiUrl,'')
- return response.content
- }
- //創(chuàng)建倉庫文件
- def CreateRepoFile(projectId,filePath,fileContent){
- apiUrl = "projects/${projectId}/repository/files/${filePath}"
- reqBody = """{"branch": "master","encoding":"base64", "content": "${fileContent}", "commit_message": "create a new file"}"""
- response = HttpReq('POST',apiUrl,reqBody)
- println(response)
- }
- //更改提交狀態(tài)
- def ChangeCommitStatus(projectId,commitSha,status){
- commitApi = "projects/${projectId}/statuses/${commitSha}?state=${status}"
- response = HttpReq('POST',commitApi,'')
- println(response)
- return response
- }
- //獲取項目ID
- def GetProjectID(repoName='',projectName){
- projectApi = "projects?search=${projectName}"
- response = HttpReq('GET',projectApi,'')
- def result = readJSON text: """${response.content}"""
- for (repo in result){
- // println(repo['path_with_namespace'])
- if (repo['path'] == "${projectName}"){
- repoId = repo['id']
- println(repoId)
- }
- }
- return repoId
- }
- //刪除分支
- def DeleteBranch(projectId,branchName){
- apiUrl = "/projects/${projectId}/repository/branches/${branchName}"
- response = HttpReq("DELETE",apiUrl,'').content
- println(response)
- }
- //創(chuàng)建分支
- def CreateBranch(projectId,refBranch,newBranch){
- try {
- branchApi = "projects/${projectId}/repository/branches?branch=${newBranch}&ref=${refBranch}"
- response = HttpReq("POST",branchApi,'').content
- branchInfo = readJSON text: """${response}"""
- } catch(e){
- println(e)
- } //println(branchInfo)
- }
- //創(chuàng)建合并請求
- def CreateMr(projectId,sourceBranch,targetBranch,title,assigneeUser=""){
- try {
- def mrUrl = "projects/${projectId}/merge_requests"
- def reqBody = """{"source_branch":"${sourceBranch}", "target_branch": "${targetBranch}","title":"${title}","assignee_id":"${assigneeUser}"}"""
- response = HttpReq("POST",mrUrl,reqBody).content
- return response
- } catch(e){
- println(e)
- }
- }
- //搜索分支
- def SearchProjectBranches(projectId,searchKey){
- def branchUrl = "projects/${projectId}/repository/branches?search=${searchKey}"
- response = HttpReq("GET",branchUrl,'').content
- def branchInfo = readJSON text: """${response}"""
- def branches = [:]
- branches[projectId] = []
- if(branchInfo.size() ==0){
- return branches
- } else {
- for (branch in branchInfo){
- //println(branch)
- branches[projectId] += ["branchName":branch["name"],
- "commitMes":branch["commit"]["message"],
- "commitId":branch["commit"]["id"],
- "merged": branch["merged"],
- "createTime": branch["commit"]["created_at"]]
- }
- return branches
- }
- }
- //允許合并
- def AcceptMr(projectId,mergeId){
- def apiUrl = "projects/${projectId}/merge_requests/${mergeId}/merge"
- HttpReq('PUT',apiUrl,'')
- }
2.3 共享庫配置
演示效果:上傳了兩個小視頻,可以掃描進入視頻號查看。