JenkinsShareLibrary實踐之自定義通知器


與任何編程環境一樣,在Jenkins流水線中,集中化功能,共享公共代碼和代碼重用都是快速、有效地進行開發的基本技術,這些實踐鼓勵使用標准方法來調用功能,為更復雜的操作創建構建塊並隱藏復雜性。他們還可以用於提供一致性以及鼓勵約定優於配置以簡化任務。

Jenkins允許用戶完成所有這些操作的一個關鍵方法就是使用流水線共享庫(pipeline share library)。共享流水線庫是由存儲在代碼倉庫中的代碼組成的,該代碼倉庫由Jenkins自動下載並可供流水線使用。

以上中文描述來自《Jenkins 2權威指南》。
關於jenkins pipeline share library的更多介紹,可以參考官方文檔

1、需求引入

隨着devops理念在公司越來越多的實踐,jenkins等工具的應用場景越來越多,當我們在執行完成某個流水線任務后,常常需要關注的是這個任務為什么執行,執行成功與否等等。於是就需要在執行完流水線后進行一定程度的消息推送,在現今的工作流中消息推送無外乎分為兩大類:郵件和企業溝通協作軟件,相比之下,我們可能更多的會去關注和使用溝通軟件來發送消息而不是通過郵件的方式。而常用的企業溝通協作軟件有以下幾類:騰訊系的企業微信、阿里系的釘釘、字節跳動的飛書等等,當然有能力的企業也會自己研發這類軟件。

本文示例以釘釘為例,通過流水線共享庫實現自定義消息通知器。

2、釘釘機器人

釘釘的群機器人是釘釘群的高級擴展功能。群機器人可以將第三方服務的信息聚合到群聊中,實現自動化的信息同步。例如:通過聚合GitHubGitLab等源碼管理服務,實現源碼更新同步;通過聚合TrelloJIRA等項目協調服務,實現項目信息同步。不僅如此,群機器人支持Webhook協議的自定義接入,支持更多可能性。

自定義釘釘機器人支持以下類型消息類型數據格式的推送,更多定義方法可參考官方的接口文檔

  • text類型
  • markdown類型
  • 整體跳轉ActionCard類型
  • 獨立跳轉ActionCard類型
  • FeedCard類型

釘釘機器人在2019年的下半年進行過升級,在新增機器人時,需要選擇一種安全條件(自定義關鍵詞、加簽、ip地址或ip地址段)來保障自定義機器人的安全。可以理解為即使機器人的token泄漏,如果不知道設置的安全條件是什么,還是無法盜用的。

3、jenkins消息推送插件

這里要提到的是在jenkins插件列表中有一個釘釘插件

簡單對此插件做了下分析:截止目前此插件在20201月份有相應代碼提交,並且發布了2.0版本,從jenkins插件官網中可以看到此版本的插件在在消息中支持了更多內容,效果如下,但是此插件目前還暫不支持流水線中使用

在此之前的上一版本提交記錄已經是2018年了,此插件使用方法類似,推送的消息效果如下

此版本支持在流水線中使用,相應內容如下

dingTalk accessToken: "xxx", 
imageUrl: "xxx", 
jenkinsUrl: "https://127.0.0.1:8080", 
message: "項目構建成功", 
notifyPeople: "155xxxx5533"

如上所示,在流水線腳本中配置釘釘機器人token、圖片路徑、jenkins地址、消息內容、要提醒的人手機號碼即可,可以發現,此消息還是有局限性,不夠友好。

因此在沒有編寫插件能力的情況下,我們可以通過更為靈活的自定義流水線共享庫的形式,並且按照釘釘機器人的官方接口文檔,自定義一個消息推送通知器。

4、自定義通知器的實現

4.1、內容定義

無論jenkins任務的構建觸發原因是使用者手動構建或通過代碼推送的自動觸發,往往關注此消息的人群是開發者們。因此通過一段時間的需求調研以及綜合各方的建議,最終將消息推送的內容中包含了以下信息:

  • 應用名稱
  • 構建結果
  • 當前版本
  • 構建發起
  • 持續時間
  • 構建日志
  • 更新記錄(包含用戶提交的短日志,用戶名稱,提交時間)

每次構建結果通知中包含了以上就基本完備。

4.2、共享庫創建

本文不過多介紹共享庫具體的創建與在pipeline流水線中的引用方法,整體來說,共享庫的代碼目錄結構如下

(root)
+- src                     # Groovy source files
|   +- org
|       +- foo
|           +- Bar.groovy  # for org.foo.Bar class
+- vars
|   +- foo.groovy          # for global 'foo' variable
|   +- foo.txt             # help for 'foo' variable
+- resources               # resource files (external libraries only)
|   +- org
|       +- foo
|           +- bar.json    # static helper data for org.foo.Bar

官方描述:
src目錄應該看起來像標准的Java源目錄結構。當執行流水線時,該目錄被添加到類路徑下。

vars目錄定義可從流水線訪問的全局變量的腳本。 每個 *.groovy文件的基名應該是一個Groovy (~ Java) 標識符, 通常是camelCased。 匹配*.txt, 如果存在, 可以包含文檔, 通過系統的配置標記格式化從處理 (所以可能是HTML, Markdown等,雖然txt擴展是必需的)。

這些目錄中的Groovy源文件 在腳本化流水線中的CPS transformation一樣。

resources目錄允許從外部庫中使用 libraryResource步驟來加載有關的非Groovy文件。 目前,內部庫不支持該特性。

根目錄下的其他目錄被保留下來以便於將來的增強。

4.3、方法的具體實現

定義共享庫中src/org/devops目錄為共享庫方法的主目錄,在這個目錄下創建一個名為dingmes.groovy的文件作為釘釘消息推送方法的代碼文件。

構建一個消息通知器的主要思路:

  • 消息指標內容從哪來
  • 消息模板如何定義
  • 消息怎么發送,發到哪里

4.3.1、消息來源

首先,消息內容從哪來,上面提到的需要在消息中體現的每個指標的可取的獲取方式

指標名稱 指標來源定義
應用名稱 定義為jenkins的任務名稱,通過全局變量env.JOB_NAME獲取或者在pipeline中自定義一個變量給出
構建結果 在pipeline中post字段指標判斷並給出
當前版本 定義為jenkins的構建編號,通過全局變量env.BUILD_NUMBER或者在pipeline中自定義版本號
構建發起 通過全局變量env.BUILD_USER獲取
持續時間 通過全局變量currentBuild.durationString獲取,這個值更為友好
構建日志 日志太多,給個鏈接即可,通過全局變量env.BUILD_URL/console獲取
更新記錄 這個指標是指代碼提交到版本庫中的更新信息,而且包含提交時間,提交者名稱,獲取思路可以通過在檢出代碼后通過類似git log的命令過濾出或者根據全局變量currentBuild.changeSet獲取

分析:
本文中的共享庫用於jenkins+k8s自動化ci測試環境,因此某些指標的定義方法為:
應用名稱自定義,用變量給出,在pipeline前文定義全局變量,在這里傳入變量即可
當前版本自定義,以代碼分支+commitid作為docker鏡像的tag,在pipeline前文中實現或亦通過共享庫實現,在這里傳入變量即可
更新記錄根據全局變量獲取,在這里通過代碼實現

較為復雜的是如何解讀currentBuild.changeSet這個全局變量,通過jenkins上的全局變量列表文檔查看如下

點擊其中的鏈接查看官方文檔

通過進一步查看官方文檔得知,currentBuild.changeSet返回的是一個集合,這個集合中包含了提交日志,commitid,作者id,作者全稱,時間戳等信息,具體對象相關屬性如下

currentBuild.changeSets{
    items[{
        msg //提交注釋
        commitId //提交hash值
        author{ //提交用戶相關信息
            id
            fullName
        }
        timestamp
        affectedFiles[{ //受影響的文件列表
            editType{
                name
            } 
            path: "path"
        }]
        affectedPaths[// 受影響的目錄,是個Collection<String>
            "path-a","path-b"
        ]
    }]
}

因此,可以通過循環遍歷得出我們需要的相關屬性值,通過groovy腳本定義方法並返回相應字符串,其中為了更優化,需要對提交日志做一下長度限制,對時間戳進行格式化,這兩個功能需要不斷調試。其中changeString變量的賦值格式定義為markdown的無序列表,最終方法如下

def getChangeString() {
    def changeString = ""
    def MAX_MSG_LEN = 20
    def changeLogSets = currentBuild.changeSets
    for (int i = 0; i < changeLogSets.size(); i++) {
        def entries = changeLogSets[i].items
        for (int j = 0; j < entries.length; j++) {
            def entry = entries[j]
            truncatedMsg = entry.msg.take(MAX_MSG_LEN)
            commitTime = new Date(entry.timestamp).format("yyyy-MM-dd HH:mm:ss")
            changeString += " - ${truncatedMsg} [${entry.author} ${commitTime}]\n"
        }
    }
    if (!changeString) {
        changeString = " - No new changes"
    }
    return (changeString)
}

4.3.2、消息模板定義

消息中的相關字段都獲取到了,下一步需要做的就是定義一個消息模板,如果使用郵件發送通知,同樣的也需要定義一個模板。

這里使用更為友好的markdown格式來發送通知,釘釘機器人接口接收的消息是json格式,具體內容可以通過查看官方文檔,為了避免換行出錯,手動指定換行符,最終的json格式數據和markdown格式模板如下

{
    "msgtype":"markdown",
    "markdown":{
        "title":"項目構建信息",
        "text":"### 構建信息\n>- 應用名稱: **${AppName}**\n- 構建結果: **${Status} ${CatchInfo}**\n- 當前版本: **${ImageTag}**\n- 構建發起: **${env.BUILD_USER}**\n- 持續時間: **${currentBuild.durationString}**\n- 構建日志: [點擊查看詳情](${env.BUILD_URL}console)\n#### 更新記錄: \n${ChangeLog}"
    },
    "at":{
        "atMobiles":[
            "155xxxx5533"
        ],
        "isAtAll":false
    }
}

4.3.3、消息發送方法

在流水線中按照消息模板渲染好的消息發送給釘釘的接口地址,可以實現的方法包括但不限於以下幾種:

  • 通過執行shell命令發送,例如curl命令指定參數即可,最為簡單,但不夠友好
  • 通過pipeline語法和插件實現,例如使用HTTP Request插件,在Jenkins pipeline中發送HTTP請求給釘釘接口。
  • 通過調用其他腳本發送,例如python腳本,較復雜,不推薦。

綜上比較,選擇一種友好且不復雜的方案,即通過pipeline語法和插件實現

首先在插件安裝中安裝好HTTP Request插件,打開語法片段生成器查看對應語法

相應參數對應如下:
httpRequest步驟返回的response對象包含兩個字段。

  • content:響應內容。
  • status:響應碼。 以下是httpRequest步驟支持的參數。
  • url:字符串類型,請求URL。
  • acceptType:枚舉類型,HTTP請求Header的“Accept”的值類型為NOT_SET、 TEXT_HTML、TEXT_PLAIN、APPLICATION_FORM、APPLICATION_JSON、 APPLICATION_JSON_UTF8、APPLICATION_TAR、APPLICATION_ZIP、 APPLICATION_OCTETSTREAM。
  • authentication:字符串類型,Username with password憑證的ID,采用的是HTTP Basic認證方式。
  • consoleLogResponseBody:布爾類型,是否將請求的響應body打印出來。
  • contentType:枚舉類型,HTTP請求Header的“Content-type”的值類型,與acceptType 支持的枚舉一樣。
  • customHeaders:HttpRequestNameValuePair對象數組,HTTP請求Header部分的內 容,該對象有3個參數。 ◦ name:字符串類型,Header名稱。 ◦ value:字符串類型,Header值。 ◦ maskValue:布爾類型,是否隱藏Header值。如果設置為true,則在打印時使用“*”代 替。 • httpMode:枚舉類型,HTTP方法,有GET(默認)、HEAD、POST、PUT、 DELETE、OPTIONS、PATCH。
  • httpProxy:字符串類型,HTTP代理地址
  • ignoreSslErrors:布爾類型,是否忽略SSL錯誤。
  • requestBody:字符串類型,請求的body內容。
  • timeout:整型,超時時間,單位為秒。默認值為0,代表不設置超時時間。
  • validResponseCodes:字符串類型,代表HTTP請求成功的狀態碼。它支持3種格式的 值。 ◦ 單狀態值:比如200,當收到200響應狀態碼時,表示HTTP請求成功。 ◦ 多狀態值:當響應狀態碼符合多個狀態碼中的一個時,代表請求成功。多個狀態碼 之間使用逗號(,)分隔。比如200,404,500。 ◦ 范圍狀態值:格式為“From:To”。比如200:302,代表收到200到302的響應狀態碼 都代表請求成功。
  • validResponseContent:字符串類型,比如設置它的值為“showme.codes”,那么只有 當HTTP返回的內容中包含了“showme.codes”時,才代表請求成功。
  • quiet:布爾類型,是否關閉所有的日志打印,默認值為false。
  • responseHandle:枚舉類型,獲取HTTP響應內容的方式。其值可以為 ◦ NONE:不讀取響應內容。 ◦ LEAVE_OPEN:當執行完請求后,並不會返回響應的內容,而是返回一個打開了的 inputStream,由你自己決定該如何讀取響應內容。但是在使用完之后,記得調用inputStream的close()方法關閉。 ◦ STRING(默認值):將響應內容轉換成一個字符串。
  • outputFile:字符串類型,請求響應內容的輸出路徑。

雖然參數有些多,但是只有url是必需的,其他參數都是可選的。這里我們傳入請求內容以及url,並省去其他不必要的參數,如下

httpRequest acceptType: 'APPLICATION_JSON_UTF8', 
        consoleLogResponseBody: false, 
        contentType: 'APPLICATION_JSON_UTF8', 
        httpMode: 'POST', 
        ignoreSslErrors: true, 
        requestBody: ReqBody, 
        responseHandle: 'NONE', 
        url: "${DingTalkHook}",
        quiet: true

4.3.4、最終方法

綜上所述,在調用此共享庫方法時傳入應用名稱變量AppName、應用版本(鏡像tag)變量ImageTag、構建狀態變量Status、以及在pipeline前文中實現的異常信息捕捉變量CatchInfo,並結合前面實現的方法內容,最終方法dingmes.groovy內容如下

/* dingmes.groovy
   ##################################################
   # Created by SSgeek                              #
   #                                                #
   # A Part of the Project jenkins-library          #
   ##################################################
*/

package org.devops

def getChangeString() {
    def changeString = ""
    def MAX_MSG_LEN = 20
    def changeLogSets = currentBuild.changeSets
    for (int i = 0; i < changeLogSets.size(); i++) {
        def entries = changeLogSets[i].items
        for (int j = 0; j < entries.length; j++) {
            def entry = entries[j]
            truncatedMsg = entry.msg.take(MAX_MSG_LEN)
            commitTime = new Date(entry.timestamp).format("yyyy-MM-dd HH:mm:ss")
            changeString += " - ${truncatedMsg} [${entry.author} ${commitTime}]\n"
        }
    }
    if (!changeString) {
        changeString = " - No new changes"
    }
    return (changeString)
}

def HttpReq(AppName,ImageTag=' ',Status,CatchInfo=' '){
    wrap([$class: 'BuildUser']){
        def DingTalkHook = "https://oapi.dingtalk.com/robot/send?access_token=67449753547bfcb8e2ee6088fdebaf2cdc7228787201fca83406fc449ffaf92"
        def ChangeLog = getChangeString()
        def ReqBody = """{
            "msgtype": "markdown",
            "markdown": {
                "title": "項目構建信息",
                "text": "### 構建信息\n>- 應用名稱: **${AppName}**\n- 構建結果: **${Status} ${CatchInfo}**\n- 當前版本: **${ImageTag}**\n- 構建發起: **${env.BUILD_USER}**\n- 持續時間: **${currentBuild.durationString}**\n- 構建日志: [點擊查看詳情](${env.BUILD_URL}console)\n#### 更新記錄: \n${ChangeLog}"
            },
            "at": {
                "atMobiles": [
                    "155xxxx5533"
                ], 
                "isAtAll": false
                }
            }"""
        // println(currentBuild.description)
        // println(currentBuild.changeSets)
        httpRequest acceptType: 'APPLICATION_JSON_UTF8', 
                consoleLogResponseBody: false, 
                contentType: 'APPLICATION_JSON_UTF8', 
                httpMode: 'POST', 
                ignoreSslErrors: true, 
                requestBody: ReqBody, 
                responseHandle: 'NONE', 
                url: "${DingTalkHook}",
                quiet: true
    }
}

4.4、方法調用

此消息通知的方法通常在pipelinepost部分調用,如下所示

post{
    success{
        script{
            tools.PrintMes("========pipeline executed successfully========",'green')
            dingmes.HttpReq(AppName,ImageTag,"構建成功 ✅")
        }
    }
    failure{
        script{
            tools.PrintMes("========pipeline execution failed========",'red')
            dingmes.HttpReq(AppName,ImageTag,"構建失敗 ❌",CatchInfo)
        }
    }
    unstable{
        script{
            tools.PrintMes("========pipeline execution unstable========",'red')
            dingmes.HttpReq(AppName,ImageTag,"構建失敗 ❌","不穩定異常")
        }
    }
    aborted{
        script{
            tools.PrintMes("========pipeline execution aborted========",'blue')
            dingmes.HttpReq(AppName,ImageTag,"構建失敗 ❌","暫停或中斷")
        }
    }
}

4.5、最終效果

測試代碼提交,執行流水線,最終的消息通知效果如下圖

5、總結

至此,本文記錄通過自定義jenkins pipeline流水線共享庫方法,實現了較為靈活的自定義釘釘機器人消息通知。如果是使用企信等其他軟件,與此實現思路相近。

參考:https://jenkins.io/doc/book/pipeline/shared-libraries/

轉載請注明出處,文章轉自山山仙人博客


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM