在命令列環境下執行程式,這個世界普遍有個共識,那就是應用程式的結束狀態碼(Exit Code)為 0
時,就會被視為是「沒有錯誤」的結果。任何非 0 的結束狀態碼,都代表有一定程度的錯誤發生。因此在 Azure Pipelines 或任何其他 CI 平台上,預設遇到應用程式回傳 Non-Zero 的結束狀態碼,就會自動報錯。誰知道 ROBOCOPY 原來複製成功,也會回傳非 0
的結束狀態碼!
原始的命令
以下就是我手動執行命令的腳本,測試過很多遍,是沒問題的:
$SourceRoot = 'D:\Projects\MyProject'
$TomcatAppPath = 'C:\Program Files\Apache Software Foundation\Tomcat 8.5\webapps'
ROBOCOPY /E "$SourceRoot\web\build\war-tmp" "$TomcatAppPath\web" /NP /NFL /NDL /NJH /NJS
ROBOCOPY /E "$SourceRoot\api\build\war-tmp" "$TomcatAppPath\api" /NP /NFL /NDL /NJH /NJS
修改過的命令
$SourceRoot = 'D:\Projects\MyProject'
$TomcatAppPath = 'C:\Program Files\Apache Software Foundation\Tomcat 8.5\webapps'
ROBOCOPY /E "$SourceRoot\web\build\war-tmp" "$TomcatAppPath\web" /NP /NFL /NDL /NJH /NJS
if( $LASTEXITCODE -ge 8 ) {
throw ("An error occured while copying to $TomcatAppPath\web. [RoboCopyCode: $($LASTEXITCODE)]")
} else {
$global:LASTEXITCODE = 0;
}
ROBOCOPY /E "$SourceRoot\api\build\war-tmp" "$TomcatAppPath\api" /NP /NFL /NDL /NJH /NJS
if( $LASTEXITCODE -ge 8 ) {
throw ("An error occured while copying to $TomcatAppPath\api. [RoboCopyCode: $($LASTEXITCODE)]")
} else {
$global:LASTEXITCODE = 0;
}
修改 Azure Pipelines 專用的命令
$SourceRoot = '$(Build.SourcesDirectory)'
$TomcatAppPath = '$(Build.ArtifactStagingDirectory)\Apache\Tomcat85\webapps'
ROBOCOPY /E "$SourceRoot\web\build\war-tmp" "$TomcatAppPath\web" /NP /NFL /NDL /NJH /NJS
if( $LASTEXITCODE -ge 8 ) {
throw ("An error occured while copying to $TomcatAppPath\web. [RoboCopyCode: $($LASTEXITCODE)]")
} else {
$global:LASTEXITCODE = 0;
}
ROBOCOPY /E "$SourceRoot\api\build\war-tmp" "$TomcatAppPath\api" /NP /NFL /NDL /NJH /NJS
if( $LASTEXITCODE -ge 8 ) {
throw ("An error occured while copying to $TomcatAppPath\api. [RoboCopyCode: $($LASTEXITCODE)]")
} else {
$global:LASTEXITCODE = 0;
}
為什麼不容易發現問題
我個人實作 Pipelines 的方式,都是先將所有步驟寫成指令碼,然後再搬移到 Pipelines 執行。如果你可以手動透過命令執行成功,那麼套用到 CI/CD 肯定也沒問題!
為什麼我會沒發現 ROBOCOPY 複製成功時並非回傳狀態碼 0
的結果呢?因為我是這樣做的:
- 先寫好指令,並手動測試個幾次
- 確認指令沒問題,確認回傳狀態碼為
0
- 設定 Azure Pipelines 並測試執行,得到回傳狀態碼為
1
(見鬼啦~)
- 重新用手動執行,確認回傳狀態碼為
0
- 再測試一次 Pipeline 並得到回傳狀態碼為
1
(見鬼啦~)
- 無限鬼打牆... ♾
這是因為我每次手動部署的時候,其實在 $(Build.ArtifactStagingDirectory)
目錄下都已經有檔案,而 ROBOCOPY 只有一種條件會回傳狀態碼 0
,那就是 No errors occurred, and no copying was done.
(沒有錯誤發生,且沒有檔案被複製)。但是 Azure Pipelines 被觸發執行的時候,會清除所有 $(Build.ArtifactStagingDirectory)
目錄下的內容,這才發生這種鬼打牆的事件!
深入 ROBOCOPY 結束狀態碼
深入瞭解之後我才發現,原來 ROBOCOPY 的結束狀態碼只要是 0
~ 7
都屬於正常結束,大於等於 8
的結束狀態碼才是有問題的。
ROBOCOPY 的狀態碼比一般命令列程式還複雜許多,他使用 bitmap 或稱 flags 來代表多種狀態的組合,基本的 flag 有以下這些,可以自由組合:
- 結束狀態碼
0
代表的意義是
- No errors occurred, and no copying was done. The source and destination directory trees are completely synchronized.
- 結束狀態碼
1
代表的意義是
- One or more files were copied successfully. (that is, new files have arrived).
- 結束狀態碼
2
代表的意義是
- Some Extra files or directories were detected. No files were copied. Examine the output log for details.
- 結束狀態碼
4
代表的意義是
- Some Mismatched files or directories were detected. Examine the output log. Housekeeping might be required.
- 結束狀態碼
8
代表的意義是
- Some files or directories could not be copied. (copy errors occurred and the retry limit was exceeded). Check these errors further.
- 結束狀態碼
16
代表的意義是
- Serious error. Robocopy did not copy any files. Either a usage error or an error due to insufficient access privileges on the source or destination directories.
後記
我曾經聽到在網路上有人說過:「不要跟我談什麼 CI/CD,你先手動執行個 100 遍再來跟我說話。」
以往各種人工作業,大家會在無數次試誤(try and error)的過程中,流失掉許多寶貴的經驗與知識,以致於錯誤不斷再犯,無法建立起十足的信心自動化。但是導入自動化建置與部署的過程中,由於把所有過程指令化,達到 IaC (Infrastructure as Code) 的效果,讓你可以對這些腳本進行版控,累積起大大小小的經驗,就算有錯誤,修掉之後也會不斷累積自信,讓公司的自動化越做越好。
沒有累積夠多的人工建置與部署經驗,在實現 CI/CD 的過程中,會遭遇到你想像不到的各種狀況。這些年許多客戶找我們幫忙建置 CI/CD 環境,客戶雖然不擅長自動化的過程,但是我們會在導入的過程中,幫助客戶釐清各種問題,過程中不但可以發現問題,還可以開始改變觀念。
一般公司大多只想直接購買解決方案來快速解決問題,但事實上,自動化的過程其實是在累積公司資產,把各種無形的知識,透過指令碼(Code)的方式,累積成公司長久可維護的有形知識。過程中會遇到許多挑戰,把冰山下方的所有問題一一浮現,同時也能讓公司的體質變的越來越好!
相關連結