【2022鐵人賽】多專案命名規則與變數範本

我們前面會將大部份的YAML設計都改成範本的主要原因就是為了大部份的內容可以重複使用,並且在需要調整需修改的時候不需要每一個專案都去修改,所以會建立多個Azure DevOps專案應該是可以預期的。

不過每一個專案名稱除了使用在Azure DevOps專案上之外,還有程式碼的專案/方案名稱、GCP Artifact Registry的名稱、GCP CloudRun的名稱,所以命名規則儘可能一致是必要的,名稱一致在很多事情上也比較方便。

有些人可能會使用像命名空間的方式來取名,利用點符號「.」來區隔階層,有些地方無法使用點符號卻可以使用減符號「-」或底線符號「_」,甚至有些服務只允許小寫名稱。使用什麼符號這些其實都還好,只要不是DevOps專案叫AAA,程式碼專案叫BBB,使用到的服務又用CCC就沒那麼麻煩。

一旦規則清楚,許多東西其實就不見得需要放在專案的設定內,可以將共同會有的設定值提取到範本專案中來設定,這跟程式設計進行抽象化把共同的東西放到最底層(最上層)的意思是一樣的。

這時候再回頭來看一遍前一篇的YAML截圖,會發現其實有很多東西應該是可以被提取出來的,也就是變數Variables的那一個區段,很多其實在每一個專案可能都會去設定它,但是內容卻是幾乎一樣的。

像是CloudRun的Region、ProjectId就一定是不會變的,所以這一篇我們就要來將這些內容抽出來做成變數範本(variables template)。

變數改成範本的規則和其它的範本差不多,就是從Variables開始下手,在Pipeline YAML原本的Variables底下也是使用template關鍵字來引用已改為範本的內容。

不過這邊我想要把它分為三個部份,分別是:

  1. 與程式語言無關的最上層共用變數範本
  2. 與程式語言相關的變數範本
  3. 專案專用的變數範本

因為實際上可能碰到的情況是Azure DevOps內有許多不同的專案,每一個專案使用的程式語言可能都不一樣,至少如果有開發手持裝置App的需求的話,就一定會有後端語言和App語言不同的情況。碰到這種情況,有些變數就不會是另一種程式語言/範本需要設定的內容,像是圖中的slnOrCsprojName這個變數,就是為了.Net語言而設定的變數。

所以在範本專案的Git Repo裡面我會建立下面的目錄結構與檔案

variables
  variables-template.yaml
  dotnet
    variables-template.yaml
  ios
    variables-template.yaml

在variables目錄底下會有一個variables-template.yaml檔案,這裡面是存放共用的變數設定。同樣的在variables目錄底下也會有不同語言的目錄,例如dotnet、ios,然後裡面再放一個variables-template.yaml。

上面這樣子就完成了1和2的設計了,剩下的3當然就是要放在專案自己的Git Repo裡面啦!所以在專案的Pipelines Git Repo裡面也會有個variables目錄,不過底下的檔案名稱就不會有template字樣了,畢竟它不是範本專案嘛!

variables
  dotnet-variables.yaml
  ios-variables.yaml

這邊我是直接以程式語言的名稱為前綴命名,方便一眼就識別出來是哪一個程式語言使用的變數檔,如果要分得再細一點的話,其實應該會再區分是給PR Pipeline使用的?還是給CI Pipeline使用的?例如:dotnet-ci-variables.yaml

知道檔案怎麼區分之後,接下來就直接把現有的變數移到各自的檔案中吧!

# variables/variables-template.yaml

variables:
  templateResourceName: templates
  pipelineResourceName: pipelines
  sourceResourceName: sources
  lowerCaseProjectName: $[lower(variables['normalProjectName'])]
  hyphenLowerCaseProjectName: $[replace(variables['lowerCaseProjectName'], '.', '-')]
  pipelineArtifact: output
  buildResultZipName: buildResult.zip

  #==== docker image configuration ====
  imgRepository: asia-east1-docker.pkg.dev/feisty-mechanic-363012/$(lowerCaseProjectName)/web

  #==== if deploy to Cloud Run ====
  imgRegistryService: GCPArtifactRegistry-$(normalProjectName)
  cloudRunServiceName: $(lowerCaseProjectName)
  cloudRunRegion: asia-east1
  cloudRunProjectId: feisty-mechanic-363012
# variables/dotnet/variables-template.yaml

variables:
  codeType: net
  slnOrCsprojName: $(normalProjectName).sln
  buildConfiguration: 'release' # debug/release
  startFileName: $(normalProjectName).dll
# (專案的) variables/dotnet-variables.yaml

variables:
  normalProjectName: DemoProject

上面的YAML內容剛好依照了1、2、3的順序列出來,但是如果要看懂的話,可能要從3開始看,也就是在專案內的變數檔最後只剩下設定了它的專案名稱,也就是normalProjectName這個變數。normalProjectName這個變數會在共用的變數範本檔(最上面的那個)裡面被lower函數用來轉為全小寫的lowerCaseProjectName變數,另外還有一個hyphenLowerCaseProjectName變數則是將lowerCaseProjectName變數中的點符號「.」轉為減符號「-」(如果有),也就是如果專案名稱是使用命名空間的格式來取名稱,像是AAA.BBB.CCC這樣,那在hyphenLowerCaseProjectName變數中就會變成aaa-bbb-ccc,在某些服務上比較適合這樣的格式。

也因為已經將專案名稱進行了一些轉換,所以後續的其它變數就會依照需要來取用適合的變數,例如imgRepository的設定中就使用到了lowerCaseProjectName變數,而imgRegistryService則是使用了normalProjectName變數(就是Service Connection分享給其它專案時預設的格式),cloudRunServiceName則是佈署的時候使用小寫的專案名稱。

在語言差異的共用變數範本中則是將normalProjectName變數用在了sln與dll檔案名稱上,在這個變數範本中另外有一個codeType變數,是為了可以在處理程式語言的範本變多的時候,利用條件式判斷來決定要插入使用哪一個範本。

上面三個變數檔都各自就位之後,最後就是修改最上面截圖的CI Pipeline YAML內容了,修改後的內容如下:

trigger:
- none

resources:
  repositories:
  - repository: sources
    type: git
    name: DemoProject/NetApp
    ref: Develop
    trigger:
      branches:
        include:
          - Develop
  - repository: pipelines
    type: git
    name: DemoProject/Pipelines
    ref: main
  - repository: templates
    type: git
    name: ironman2022/templates
    ref: main

variables:
  - template: variables/variables-template.yaml@templates
  - template: variables/dotnet/variables-template.yaml@templates
  - template: ../variables/dotnet-variables.yaml

stages:
  - template: stages/dotnet-build-stage.yaml@templates
    parameters:
      sourcePath: $(Build.SourcesDirectory)
      slnOrCsprojName: $(slnOrCsprojName)
      artifactName: $(pipelineArtifact)
      unzip: true
      zipFileName: $(buildResultZipName)
      unzipToFolderPath: $(System.ArtifactsDirectory)/buildImage
      imgRepository: $(imgRepository)
      imgTags: |
        latest
        $(Build.BuildId)
      buildDockerfile: $(buildDockerfile)
      buildContext: $(System.ArtifactsDirectory)/buildImage
      containerRegistry: ${{ variables.imgRegistryService }}
      startFileName: $(startFileName)
      templateResourceName: ${{ variables.templateResourceName }}
  - template: stages/deploy-cloudrun-stage.yaml@templates
    parameters:
      cloudRunProjectId: $(cloudRunProjectId)
      imgRepository: $(imgRepository)
      cloudRunRegion: $(cloudRunRegion)
      cloudRunServiceName: $(cloudRunServiceName)
      templateResourceName: ${{ variables.templateResourceName }}
      pipelineResourceName: ${{ variables.pipelineResourceName }}

上面在variables區段就是全部改用了變數範本,順序就是前面提到的1、2、3,要留意的是只有第1個和第2個最後有@符號,代表引用範本專案內的檔案。

仔細看上面的YAML內容的話,你可能會覺得不太對勁,怎麼會有些地方是用$(變數名稱),有些地方卻是用${{ variables.變數名稱 }}呢?

如果你還記得認識Build Pipeline的運算式Expressions與函數這篇內容提到的巨集運算式與編譯時期的運算式的話,就會知道前者是常用的巨集式用法,後者是編譯時期的用法。因為我們定義的有些變數是內嵌式還有引用到變數(imgRegistryService變數內用到其它變數),或是其它原因,所以無法使用巨集式的用法,這時候改用後者就可以解決這樣的問題。

通常這是改完要執行的時候碰到錯誤訊息,上面會告訴你哪一些變數它當時無法解析,可以先改列出來的那些項目,或是全部都改用後者(因為目前到這裡沒有動態改到變數的地方,所以沒差)。

發佈留言

%d 位部落客按了讚: