基本版的CI Pipeline建立完之後,又花了兩篇的篇幅快速的介紹了YAML的運算式、函數、變數以及參數,一切的一切就是為了後面進入YAML範本化的設計。
雖然每個系統建立一個Azure DevOps專案之後,我們仍然可以為每個專案依照前面的Pipeline設計方式替它們建立CI Pipeline,但是每個專案內的Pipeline應該幾乎是大同小異的,除了專案名稱與一些變數的設定值差異之外,依照前面流程規劃說明的內容來看,基本動作就是程式碼Commit之後,建立PR合併程式碼,接著編譯程式、上傳成品、建立Docker Image、佈署Dev環境,這些動作在每一個專案中的Pipeline都是一致的,所以最適合的方式就是建立YAML範本,讓每一個專案引用範本,傳入不同的設定值就行了。
要把現有的YAML內容改成template其實很簡單,把它們分拆成不同的YAML檔案就行。不過在這之前我先建立了一個新的Git Repository,名稱就叫作templates,使用預設的main分支,專門用來存放改為範本的YAML檔案。(建立Git Repo的部份忘了請參考先前的文章)
接著利用前面這篇最後完成的CI Pipeline YAML內容為例,我們將BuildCode這個Job中的內容抽離出來變成範本,在templates git repo中存放在jobs/buildCode.yaml。
jobs:
- job: BuildCode
steps:
- checkout: sources
clean: true
- task: Bash@3
displayName: Check folder exist or create
inputs:
targetType: 'inline'
script: |
if [ -d "$(Build.ArtifactStagingDirectory)/pipelineFiles/" ]; then echo "$(Build.ArtifactStagingDirectory)/pipelineFiles/ exist"; else mkdir -p "$(Build.ArtifactStagingDirectory)/pipelineFiles/"; fi
- task: PowerShell@2
displayName: 'Get git commit sha'
inputs:
targetType: 'inline'
workingDirectory: $(Build.SourcesDirectory)
pwsh: true
script: |
$gitFullCommitSHA = git rev-parse HEAD
$gitCommitSHA = $gitFullCommitSHA.Substring(0,8)
echo "git commit sha: $gitCommitSHA"
echo "git full commit sha: $gitFullCommitSHA"
echo "$gitFullCommitSHA" > $(Build.ArtifactStagingDirectory)/pipelineFiles/gitCommitSHA.txt
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/pipelineFiles'
ArtifactName: 'PipelineFiles'
publishLocation: 'Container'
- script: |
export UID=$(id -u)
export GID=$(id -g)
docker run --user $UID:$GID --rm \
-v $(Build.SourcesDirectory):/tmp/source \
-v $(Build.BinariesDirectory):/tmp/publish \
-e DOTNET_CLI_HOME=/tmp/.dotnet \
mcr.microsoft.com/dotnet/sdk:6.0-alpine \
dotnet publish /tmp/source/$(slnOrCsprojName) \
-c release \
-o /tmp/publish
displayName: Dotnet Build
- task: ArchiveFiles@2
displayName: 壓縮成zip
inputs:
rootFolderOrFile: $(Build.BinariesDirectory)
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/zipFiles/$(buildResultZipName)'
replaceExistingArchive: true
- task: PublishBuildArtifacts@1
displayName: 上傳到Pipeline Artifact
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/zipFiles/$(buildResultZipName)'
ArtifactName: '$(pipelineArtifact)'
publishLocation: 'Container'
這邊要特別注意的地方就是最前面的jobs,雖然我們的目的是把BuildCode這個Job抽出來變成範本,但是範本並沒有限制只能有一個子項目,所以Job的範本必須以jobs為開頭。
把上面的內容存在templates git repo的jobs目錄下,檔名為buildCode.yaml,Commit到main分支之後,就可以將原本CI Pipeline的BuildCode部份移掉,改用template關鍵字來設定。不過在改用template關鍵字設定之前,還必須在resources底下加入對於templates git repo的資源設定:
resources:
repositories:
- repository: sources
type: git
name: ironman2022/NetApp
ref: Develop
trigger:
branches:
include:
- Develop
# 加入下面這段
- repository: templates
type: git
name: ironman2022/templates
ref: main
接著就把下面的BuildCode移除,改用下面這一行來取用範本內容,路徑是相對templates git repo的路徑,@符號後面的名稱是在resources定義repository的名稱,如果上面是設定為- repository: ABCD,那就是@ABCD。
jobs:
- template: jobs/buildCode.yaml@templates
完整的YAML內容如下:
trigger:
- none
resources:
repositories:
- repository: sources
type: git
name: ironman2022/NetApp
ref: Develop
trigger:
branches:
include:
- Develop
- repository: templates
type: git
name: ironman2022/templates
ref: main
variables:
pipelineArtifact: output
buildResultZipName: buildResult.zip
slnOrCsprojName: IronmanWeb.sln
imgRepository: 'asia-east1-docker.pkg.dev/feisty-mechanic-363012/ironman2022/ironmanweb'
buildDockerfile: 'Dockerfile'
imgRegistryService: 'GCPArtifactRegistry'
cloudRunServiceName: ironmanweb
cloudRunPort: 8080
cloudRunRegion: asia-east1
cloudRunProjectId: feisty-mechanic-363012
gcpAuthJsonFile: ironman2022-gcp-key.json
jobs:
- template: jobs/buildCode.yaml@templates
- job: BuildImage
dependsOn: BuildCode
steps:
- task: DownloadBuildArtifacts@0
displayName: 下載Pipeline Artifact
inputs:
buildType: 'current'
cleanDestinationFolder: true
downloadType: 'single'
artifactName: '$(pipelineArtifact)'
downloadPath: '$(System.ArtifactsDirectory)/'
- task: ExtractFiles@1
displayName: Unzip zip
inputs:
archiveFilePatterns: '$(System.ArtifactsDirectory)/$(pipelineArtifact)/$(buildResultZipName)'
destinationFolder: '$(System.ArtifactsDirectory)/BuildImage'
cleanDestinationFolder: true
overwriteExistingFiles: true
- task: Docker@2
displayName: Build image
inputs:
repository: '$(imgRepository)'
command: 'build'
Dockerfile: $(buildDockerfile)
buildContext: '$(System.ArtifactsDirectory)/BuildImage'
arguments: '--no-cache'
tags: |
latest
- task: Docker@2
displayName: "Login to Container Registry"
inputs:
command: login
containerRegistry: $(imgRegistryService)
- task: Bash@3
displayName: Push docker image
inputs:
targetType: 'inline'
script: |
docker push -a $(imgRepository)
- job: DeployCloudRun
dependsOn: BuildImage
steps:
- task: Bash@3
displayName: Deploy docker image to cloudrun
inputs:
targetType: 'inline'
script: |
docker run --rm \
-v $(Build.SourcesDirectory)/$(gcpAuthJsonFile):/gcp/cloudKey.json \
asia.gcr.io/google.com/cloudsdktool/google-cloud-cli:latest \
bash -c "gcloud auth login --cred-file=/gcp/cloudKey.json && gcloud run deploy $(cloudRunServiceName) --set-env-vars=Ironman=$(Build.BuildId) --image $(imgRepository) --region $(cloudRunRegion) --project $(cloudRunProjectId) --allow-unauthenticated"
修改之後再次執行Pipeline,是不是和原本的結果一樣呢?