The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

在 Azure Pipelines 裡面正確使用 ROBOCOPY 複製檔案的方法

在命令列環境下執行程式,這個世界普遍有個共識,那就是應用程式的結束狀態碼(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 的結果呢?因為我是這樣做的:

  1. 先寫好指令,並手動測試個幾次
  2. 確認指令沒問題,確認回傳狀態碼為 0
  3. 設定 Azure Pipelines 並測試執行,得到回傳狀態碼為 1(見鬼啦~)
  4. 重新用手動執行,確認回傳狀態碼為 0
  5. 再測試一次 Pipeline 並得到回傳狀態碼為 1(見鬼啦~)
  6. 無限鬼打牆... ♾

這是因為我每次手動部署的時候,其實在 $(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)的方式,累積成公司長久可維護的有形知識。過程中會遇到許多挑戰,把冰山下方的所有問題一一浮現,同時也能讓公司的體質變的越來越好!

相關連結

留言評論