Why Jenkins Pipeline

談到 CI/CD 最為人所知,學習資源也最豐富的莫過於老古董 Jenkins 了吧。 Jenkins 自身有相當多的 plugin 可供選擇,讓建制的流程可以非常客製化,而且自從它推出了 pipeline 之後,讓這個調整跟變化顯的更加容易,至少對 RD 來講是這樣啦,看起來更像是 Code as infrastructure。

雖然 Jenkins pipeline 提供了兩種不同的語法樣式來實做,聲明式語法(declarative syntax)、腳本式語法(scripted syntax)。

這兩種語法基本上可以適用在每個情境下,聲明式能做到的腳本式也能做到,反之亦然。所以要用那一個就真的是看個人喜好跟習慣了。

Declarative syntax聲明式語法Scripted syntax腳本式語法
Pros:更加貼近傳統的Web表單形式,轉換上較容易可以通過Blue Ocean自動生成更有好的錯誤識別,語法檢查Cons:較難勝認複雜的建制流程較難實現自定義程式碼Pros:對於較複雜的建制流程支援程度比較好更靈活的自定義程式碼操作較少的規範要求Cons:語法檢查受限於Groovy語言與環境與傳統Web表單形式差很大,轉換上較難

Management Plugin DotNet Solution Need

若要建制 .NET Solution 的話那 CI 機器上肯定要安裝 MSBuild (2015, 2019),再用 Web Platform Intaller 安裝 Web Deploy,當然還要有 .NET Framework (4.8, 4.6.2, 3.5 的話需要在 Windows 10/8 內啟用即可) 安裝好,基本上這樣對於一個 .NET Solution 的建制環境來說就大抵可用了。

而開發環境裝好了當然就是要來處理一下版控的環境 git,而要在 windows 身上安裝 git 本身並不難,按照官方給的指示來安裝就沒什麼問題。

一個基本的 .NET Solution Pipeline

在建制之前有一個很重要的點就是把 Code 先拿下來,這邊示範的是讓使用者選擇要用那一個 branch 或是 tag,再用那個 Commit 來抓 Code。

 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
properties([
    //前面這兩個設定第一個是限制保留最近幾次的建制結果,第二個則是限制不能同時建制
    buildDiscarder(logRotator(numToKeepStr: '10')),
    disableConcurrentBuilds(),
    parameters([
        [
            $class: 'GitParameterDefinition',
            branch: '',
            branchFilter: '.*',
            defaultValue: 'origin/master',
            description: 'Select which git branch will be used',
            name: 'GIT_BRANCH',
            quickFilterEnabled: true,
            selectedValue: 'NONE',
            sortMode: 'NONE',
            tagFilter: '*',
            type: 'PT_BRANCH_TAG'
        ]
    ])
])
stage('Checkout Code') {
        // get source from Git
        checkout([
            $class: 'GitSCM',
            branches: [[name: "${GIT_BRANCH}"]],
            userRemoteConfigs: [[
                // 這邊的 credentialsId 可以在 jenkins 身上設定好
                credentialsId: 'your repo credential set in jenkins'
                // 除了一般的 gitlab 或是 github 位置,其實你可以選擇本機位置當測試使用
                url: 'your repo path'
            ]],
            doGenerateSubmoduleConfigurations: false,
            extensions:[
                [$class: 'CloneOption', noTags: false, shallow: true],
                [$class: 'WipeWorkspace'],
                [$class: 'CleanBeforeCheckout'],
                [$class: 'PerBuildTag']
            ]
        ])
}

生為一個 .NET Solution 在建制前必做的事情之一就是先把相依的套件都先安裝好。這段語法主要是直接下載 nuget 並更新 nuget,再直接呼叫 restore solution 檔案即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
stage('Package Restore') {
        powershell encoding:'UTF-8', script: '''
        $nugetPath = "$env:WORKSPACE\\NuGet\\nuget.exe"
        $nugetRootPath = "$env:WORKSPACE\\NuGet"
        $url = \'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe\'
        $sourceNugetExe = "$env:WORKSPACE\\NuGet\\nuget.exe"

        if(!(Test-Path $nugetPath)){
            New-Item -ItemType Directory -Force -Path $nugetRootPath
            Invoke-WebRequest -Uri $url -OutFile $sourceNugetExe
        }

        $updatecmd = \'."$sourceNugetExe" update -self\'
        Invoke-Expression $updatecmd

        $cmd = ".\'${sourceNugetExe}\' restore \'$env:WORKSPACE\\yourproject.sln\'"
        Invoke-Expression $cmd
        '''
    }

安裝完必要套件後,就是呼叫建制指令來建制~

這邊帶的參數 Configuration=Dev 是在專案中設置了 Dev, staging, Production 這些設定,若是預設的話應該就是 Debug, Release

1
2
3
stage('Build') {
        bat """\"${msbuild}\" \"$slnPath\" /p:Configuration=Dev /p:OutputPath=\"${WORKSPACE}\\buildTempFolder\\${codeName}_${BUILD_NUMBER}\\Dev\\src\" /t:rebuild"""
    }

變數 msbuild 就是 CI 機器上面 msbuild 的路徑,或是有設定 Global Tool Configuration 的話可以使用以下語法

1
def msbuild = tool name: 'MSBuild14.0', type: 'hudson.plugins.msbuild.MsBuildInstallation'

建制完成後執行 unit test

若是使用 NUnit 的話可以在 Solution 中安裝 NUnit.ConsoleRunner 這個套件,這樣的話就可以使用 Powershell 呼叫執行程式,而其中的 returnStatus 設為 false 就是一個重點,這個設為 false 會在執行完測試時若沒有返回正常值,將會使流程失敗。

1
2
3
4
5
6
7
8
stage('Unit Test') {
        powershell encoding:'UTF-8', returnStatus: false, script: '''
        $testExecute = "$env:WORKSPACE\\packages\\NUnit.ConsoleRunner.*\\tools\\nunit3-console.exe"
        $slnPath = "$env:WORKSPACE\\buildTempFolder\\yourproject_$env:BUILD_NUMBER\\Dev\\src\\yourproject.Test.dll"
        $cmd = ".\'$testExecute\' \'$slnPath\'"
        Invoke-Expression $cmd
        '''
}

若是使用 MSTest 的話語法類似,這邊則安裝 Microsoft.TestPlatform 使用當中的執行程式,可以在 Solution 中納入下載也可以,也可以另外下載不包含在 Solution 中。這邊示範的是獨立安裝此套件,並未包含在 solution 中,所以執行路徑有所差別。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
stage('Install Test Framework') {
        powershell script: '''
        $nugetPath = "$env:WORKSPACE\\NuGet\\nuget.exe"
        $nugetRootPath = "$env:WORKSPACE\\NuGet"
        $sourceNugetExe = "$env:WORKSPACE\\NuGet\\nuget.exe"

        if(!(Test-Path $nugetPath)){
            New-Item -ItemType Directory -Force -Path $nugetRootPath
            Invoke-WebRequest -Uri $url -OutFile $sourceNugetExe
        }

        $installcmd = \'."$sourceNugetExe" install Microsoft.TestPlatform\'
        Invoke-Expression $installcmd
        '''
}
stage('Unit Test') {
        powershell encoding:'UTF-8', returnStatus: false, script: '''
        $testExecute = "$env:WORKSPACE\\Microsoft.TestPlatform.*\\tools\\net451\\Common7\\IDE\\Extensions\\TestPlatform\\vstest.console.exe"
        $slnPath = "$env:WORKSPACE\\buildTempFolder\\yourproject_$env:BUILD_NUMBER\\Dev\\src\\yourproject.Tests.dll"
        $cmd = ".\'$testExecute\' \'$slnPath\' /TestAdapterPath:\'$env:WORKSPACE\\packages\\.\'"
        Invoke-Expression $cmd
        '''
}

建制完後產出報表並顯示

若是使用 NUnit 的話這個部份會很簡單,請先確認 Jenkins 有安裝這個套件 NUnit plugin 然後在 pipeline 補上該段 stage 即可。

1
2
3
stage("PublishTestReport"){
     nunit testResultsPattern: 'TestResult.xml'
}

Reference