SonarQube-整合Jenkins Pipeline(使用jenkins套件)

先前已經有直接在 pipeline 專案內執行批次指令來分析專案,但這樣做的話其實並沒有辦法在專案分析的時候,判斷分析結果,只能依照原先做好的流程一直走下去,因此這一篇文章主要在解決這一點,透過 Jenkins 套件來處理這件事情

事前準備

整體的流程如下

  1. 專案建置、產生測試報告等等
  2. 執行分析
  3. 等候分析結果
    • 通過繼續往下
    • 失敗中止

SonarQube:新增全域 token

這裡新增的 token 請選擇 Global,這個是為了要給 Jenkins 後續分析專案、取得結果使用,那行只顯示一次的 token 也要先記起來,等等在 Jenkins 新增認證的時候會需要輸入

SonarQube:新增分析專案 webHook

要設定專案的 webHook,從下圖的地方點選進去

從右上角點新增跳出燈箱,輸入資料,名稱隨意,URL 指的是當分析完成之後 sonarQube 要打的網址,這個就填 Jenkins 主機加上sonarqube-webhook,例如:http://localhost:8080/sonarqube-webhook,最後面密碼指的是剛剛新增的全域 token,例如:sqa_e6a8c5e3a2ce39b60501c5ddc319b3c90442daf1

在分析的流程當中所需要的 token 都要用相同的,否則在 Jenkins 請求訪問分析結果的時候,會出現 http 401

Jenkins:SonarQube Scanner for Jenkins

在這個步驟裡面,我們會需要透過SonarQube Scanner for Jenkins套件來完成分析、等候的這兩個部分,從管理Jenkins->外掛總管並選擇可用的,找到套件安裝即可

Jenkins:新增認證

在 Jenkins 透過 SonarQube Scanner for Jenkins 互動的時候會需要 sonarQube 的有效 token,因此在 Jenkins Credentials 這邊也需要新增一個 Secret Text。Secret 欄位就輸入上面新增的全域 token

Jenkins:SonarQube Server 設定

接著是設定系統,在這裡需要將我們的 sonarQube server 資訊設定好,包含

  1. Name: 填寫一個名稱,後續會用到,我這邊是填寫 local-sonarqube
  2. Server URL: 這邊我的 SonarQube Server 是架在 localhost:9000
  3. Server authentication token: 使用先前建立的 sonarqube-key
  4. Webhook Secret: 使用先前建立的 sonarqube-key

Jenkins:SonarQube Scanner 設定

因為先前已經有下載過 scanner,所以我就直接把 scanner 路徑填一填,雖然他有自動安裝的選項但我沒有用過,這樣在隨後的 pipeline 就可以透過套件提供的工具 tool 來呼叫 scanner,如果是非 C# 專案,就要自己再去找找對的 scanner

pipeline 設定

執行分析的範例如下,理論上測試報告產生之前要先 build 一次最新的建置,實務上請自行斟酌作法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
node() {
ws('D:\\code\\Github\\Repos\\TaskProject') {
stage('Coverage') {
// 刪除先前產生的測試覆蓋報告
bat "del *.dcvr"
// 產生測試覆蓋報告
bat '''
"D:\\art\\programs\\JetBrains.dotCover.CommandLineTools.2022.1.2\\dotCover.exe" cover /TargetExecutable="D:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\Extensions\\TestPlatform\\vstest.console.exe" /TargetArguments="TaskProjectTests.dll" /TargetWorkingDir="TaskProjectTests\\bin\\debug" /Output="TaskProjectTests.dcvr"
'''
// 將報告轉換格式為 html
bat '"D:\\art\\programs\\JetBrains.dotCover.CommandLineTools.2022.1.2\\dotCover.exe" report --source=TaskProjectTests.dcvr --output=report.html --reportType=html'
}

stage('UnitTest') {
// 刪除先前產生的測試執行報告
bat "del testResults /s /q"
// 產生測試執行報告
bat '''
"D:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\TestWindow\\vstest.console.exe" /Logger:trx TaskProjectTests\\bin\\Debug\\TaskProjectTests.dll
'''
}


stage('SonarQube analysis') {
// 設定 sonarQube scanner 的名字(這邊的名字要跟先前在【Jenkins:SonarQube Scanner 設定】的一樣)
def scanner = tool name: 'SonarScanner.MSBuild-net46'
// 透過 sonarQube 進行分析專案的準備,這裡填寫的值都要跟先前設定的相同
withSonarQubeEnv(installationName:'local-sonarqube',credentialsId:'sonarqube-key') {
// 刪除先前分析的暫存資料(如果沒有這個可能會在分析快結束的時候出錯)
bat "del .sonarqube /s /q"
// 指定測試覆蓋報告、測試執行報告的路徑、sonarQube 位址,專案名稱
bat "${scanner}\\SonarScanner.MSBuild.exe begin /k:taskProject /d:sonar.host.url=http://localhost:9000 /d:sonar.cs.dotcover.reportsPaths=report.html /d:sonar.cs.vstest.reportsPaths=TestResults/*.trx"
// 建置專案
bat "msbuild.exe /t:Rebuild"
// 執行分析
bat "${scanner}\\SonarScanner.MSBuild.exe end"
}

script {
// 預計最多等候分析一個小時
timeout(time: 1, unit: 'HOURS') {
// 等候 sonarQube webHook,並且指定使用的是全域 token
def qg = waitForQualityGate(webhookSecretId:'sonarqube-key')
// 如果返回狀態不OK,就丟出錯誤並中斷 pipeline
if (qg.status != 'OK') {
error "Pipeline aborted due to quality gate failure: ${qg.status}"
}
}
}
}

// 這裡後續就是可以接著在寫自己想要的流程
stage('END') {
bat '@echo OK'
}
}
}

測試

這裡的測試專案我拿了一個 C# 的簡單的單元測試,因為之前有做過很多次這方面的練習了,如何分析專案的細節,怎麼加入測試數量、測試覆蓋率就不再贅述,利用 sonarQube 的 quality gate 設定,我設定了一個新增的程式碼覆蓋率必須要達到 99%才算成功,利用這個東西設定讓 Jenkins 取得分析結果是失敗的,然後看看他能不能中止

第一次先跑專案,當然先讓他過,第二次隨意加一些沒有用到的 code 並再次分析就會失敗了

在主畫面也可以看到建置紀錄與左側都有 SonarQube 的超連結,點過去就可以看到分析專案的資料了

結論

CI流程中若分析失敗,通常需要通知某些人,做某一些事情,而 SonarQube 本身的通知機制我是覺得有點難用,所以流程的事情最好還是交給 Jenkins 來處理,不管是透過 Email 通知,還是要透過 Teams 都很方便。接下來要考慮的就是什麼時間點進行分析的動作,這就看公司資源及需要了。

補充:加入信件通知

主要還是依靠外掛套件Email Extension來做,測試用的 SMTP 服務可以用 smtp4dev,寄送 EMAIL 還要去設定一下,這裡就不贅述了,pipeline 範例如下

1
2
// 寄送信件
emailext attachLog: true, body: '', recipientProviders: [buildUser(), developers()], subject: '[TaskProject] Success'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 完整範例:分析結果,成功或失敗都寄送EMAIL通知
node() {
ws('D:\\code\\Github\\Repos\\TaskProject') {

stage('Coverage') {
bat "del *.dcvr"
bat '''
"D:\\art\\programs\\JetBrains.dotCover.CommandLineTools.2022.1.2\\dotCover.exe" cover /TargetExecutable="D:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\Extensions\\TestPlatform\\vstest.console.exe" /TargetArguments="TaskProjectTests.dll" /TargetWorkingDir="TaskProjectTests\\bin\\debug" /Output="TaskProjectTests.dcvr"
'''
bat '"D:\\art\\programs\\JetBrains.dotCover.CommandLineTools.2022.1.2\\dotCover.exe" report --source=TaskProjectTests.dcvr --output=report.html --reportType=html'
}

stage('UnitTest') {
bat "del testResults /s /q"
bat '''
"D:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\TestWindow\\vstest.console.exe" /Logger:trx TaskProjectTests\\bin\\Debug\\TaskProjectTests.dll
'''
}


stage('SonarQube analysis') {
def scanner = tool name: 'SonarScanner.MSBuild-net46'
bat "@echo Scanner Path:${scanner}"
withSonarQubeEnv(installationName:'local-sonarqube',credentialsId:'sonarqube-key') { // If you have configured more than one global server connection, you can specify its name
bat "del .sonarqube /s /q"
bat "${scanner}\\SonarScanner.MSBuild.exe begin /k:taskProject /d:sonar.host.url=http://localhost:9000 /d:sonar.cs.dotcover.reportsPaths=report.html /d:sonar.cs.vstest.reportsPaths=TestResults/*.trx"
bat "msbuild.exe /t:Rebuild"
bat "${scanner}\\SonarScanner.MSBuild.exe end"
}

script {
timeout(time: 1, unit: 'HOURS') {
def qg = waitForQualityGate(webhookSecretId:'sonarqube-key')
if (qg.status != 'OK') {
emailext attachLog: true, body: '', recipientProviders: [buildUser(), developers()], subject: '[TaskProject] Failed'
error "Pipeline aborted due to quality gate failure: ${qg.status}"
}
}
}
}

stage('END') {
bat '@echo OK'
emailext attachLog: true, body: '', recipientProviders: [buildUser(), developers()], subject: '[TaskProject] Success'
}
}
}

補充:執行建置成功後觸發分析 JOB

先前的做法是在同一台主機上面執行,如果專案不多,或只是自己練習應該是合適的,但如果能夠拆開各自獨立的任務,應該更容易運用,因此將建置的部分獨立一個任務,在建置成功完畢後觸發分析任務。
因為建置任務先前採用的是 pipeline script,所以只需要透過build即可觸發任務,同時將Git Parameter傳遞出去即可,詳請請參照Jenkins 參數化建置與上下游專案的 【補充:觸發另一個任務並傳遞參數範例】一節