2019年7月25日 星期四

在Azure Devops Server 2019 的 powershell task 執行外部執行檔

在Azure Devops Server 2019 的 powershell task 執行外部執行檔

原本的需求是在 Pipelines 部署時,判斷如果docker-compose 己經有把container 建起來了,就更新container,否則就用docker-compose建立新的container。

但是在Pipelines的部署工作裡,似乎要做到上面的需求有困難。所以才想要在powershell裡執行docker-compose

底下是我預備要執行的powershell,

Set-Location -Path C:\composefile

$proxy = 'iisproxy'
$site1 = 'site-blue'
$site2 = 'site-green'

$cID = $(docker ps -qf "name=$proxy")

if(-NOT $cID){
 Write-Host "create new container"
 docker pull myhub.local/iisproxy
 docker pull myhub.local/app
        docker-compose up -d 
    }
    else {
        Write-Host "update container"
 docker pull myhub.local/app
        docker-compose stop $site1 
        docker-compose up -d --force-recreate --no-deps $site1  
 Start-Sleep -s 5
        docker-compose stop $site2
        docker-compose up -d --force-recreate --no-deps $site2        
    }

但是卻發現只要是執行到 docker-compose 就會出現 NativeCommandError 的錯誤

2019-07-24T08:00:10.7991464Z ##[error]位於 C:\azagent\A1\_work\_temp\46980d80-fab6-4a21-8fd7-1a6e7130d9a8.ps1:20 字元:9

+ docker-compose stop $site1  

2019-07-24T08:00:10.8097416Z ##[error]+ ~~~~~~~~~~~~~~~~~~~~~~~~~~  

2019-07-24T08:00:10.8204529Z ##[error] + CategoryInfo : NotSpecified: (Stopping site-blue ... :String) [], RemoteException  

2019-07-24T08:00:10.8301628Z ##[error] + FullyQualifiedErrorId : NativeCommandError  

2019-07-24T08:00:10.8403452Z ##[error]  

2019-07-24T08:00:10.8516019Z ##[error]PowerShell 已結束,代碼為 '1'。

但是直接開啟command視窗執行是正常的。 後來又發現在Powershell ISE 裡執行指令就會有一樣的錯誤。 原來兇手是 powershell 自己,害我錯怪 docker-compose 好幾天。

自此,才有比較明確的追查方向。
找到在stackoverflow上一樣症頭的討論

https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide/20950421#20950421

原來是因為 powershell 在處理 STDERR (standard error) 有不同的行為才造成這樣的錯

照文章內的好幾個做法,要在 Pipelines裡可以正常執行要改成這樣
用 2>&1 來吃掉多出來的錯誤

&cmd /c "docker-compose up -d 2>&1"

下面是完整的script

Set-Location -Path C:\composefile

$proxy = 'iisproxy'
$site1 = 'site-blue'
$site2 = 'site-green'

$cID = $(docker ps -qf "name=$proxy")

if(-NOT $cID){
 Write-Host "create new container"
 docker pull myhub.local/iisproxy
 docker pull myhub.local/app
        &cmd /c "docker-compose up -d 2>&1"
    }
    else {
        Write-Host "update container"
 docker pull myhub.local/app
        &cmd /c "docker-compose stop $site1 2>&1"
        &cmd /c "docker-compose up -d --force-recreate --no-deps $site1 2>&1"
 Start-Sleep -s 5
        &cmd /c "docker-compose stop $site2 2>&1"
        &cmd /c "docker-compose up -d --force-recreate --no-deps $site2 2>&1"
    }

在winodws container 使用 chrome headless 做 angular unit test

在winodws container 使用 chrome headless 做 angular unit test

現在 azure pipeline 來到前端的unit test 階段
想像中應該就像在自己電腦裡執行一樣,如果可以正常用chrome headless 執行 unit test 放到docker 應該不會有問題。BUT 事情總是沒有這麼單純。

以下是我部份的karma.config.js 跟DockerFile

module.exports = function (config) {
  config.set({
    customLaunchers: {
      ChromeHeadlessDocker: {
        base: 'ChromeHeadless',
        flags: [
          '--disable-web-security',
          '--no-sandbox',
          '--disable-setuid-sandbox',
          '--disable-dev-shm-usage',
          '--remote-debugging-port=9223',
          '--headless',
          '--disable-gpu'
        ]
      }
    },
    .
    .
    .
  });
};

DockerFile

RUN ng test --watch=false --code-coverage --browsers=ChromeHeadlessDocker --reporters=junit,progress

結果一直會出現

ChromeHeadless have not captured in 60000 ms, killing.

這才想到…我沒有裝chrome在docker container裡面

原本我是用

RUN Invoke-WebRequest -OutFile ChromeStandaloneSetup64.exe https://dl.google.com/tag/s/installdataindex/update2/installers/ChromeStandaloneSetup64.exe;
RUN ./ChromeStandaloneSetup64.exe /silent /install

但是卻一直裝不成功。( 如果有裝成功會在 C:\Program Files (x86)\ 底下有個Google 的目錄)
後來到 https://cloud.google.com/chrome-enterprise/browser/download/ 下載MSI的版本,先COPY到 image裡面再下指令裝

COPY ./GoogleChromeStandaloneEnterprise64.msi /
RUN Start-Process -FilePath msiexec -ArgumentList /q, /i, GoogleChromeStandaloneEnterprise64.msi -Wait ; `
Remove-Item -Force GoogleChromeStandaloneEnterprise64.msi

確認chrome有裝起來之後,再執行docker build 還是出現

ChromeHeadless have not captured in 60000 ms, killing.

把 karma.config.js 裡的 logLevel: config.LOG_INFO, 改成 config.LOG_DEBUG 也沒得到什麼有用的資訊

這個時候己經快瘋了,在這裡卡關卡了三天,無論改config或者一些奇奇怪怪的方法都沒效。

而且重點是網路上找到的方法,其實都是Linux container的解決方案。 Windows Container 根本一整個弱掉了。

後來終於在這裡找到 https://stackoverflow.com/questions/52948655/chrome-on-windows-docker-container-is-not-working 原來是缺少字型的問題. 一整個 WHAT THE ~~~

這位好心的 Prom3theu5 還提供了字型及安裝方法
https://github.com/prom3theu5/ServerCoreFonts

照著上面的方法,安裝字型之後終於可以看到開始測試的訊息了。

2019-07-08T00:39:39.6996247Z  [32m08 07 2019 08:39:39.675:INFO [karma-server]:  [39mKarma v3.1.1 server started at http://0.0.0.0:9876/

2019-07-08T00:39:39.6999650Z  [32m08 07 2019 08:39:39.698:INFO [launcher]:  [39mLaunching browsers ChromeHeadlessCI with concurrency unlimited

2019-07-08T00:39:39.7124483Z  [32m08 07 2019 08:39:39.710:INFO [launcher]:  [39mStarting browser ChromeHeadless

2019-07-08T00:40:35.5387718Z  [32m08 07 2019 08:40:35.537:INFO [HeadlessChrome 0.0.0 (Windows 10 0.0.0)]:  [39mConnected on socket Gsf6g9YFWi0lxoFVAAAA with id 57991930

2019-07-08T00:40:49.6356010Z HeadlessChrome 0.0.0 (Windows 10 0.0.0): Executed 0 of 439 SUCCESS (0 secs / 0 secs)

2019-07-08T00:40:49.7481854Z  [1A [2KHeadlessChrome 0.0.0 (Windows 10 0.0.0): Executed 1 of 439 SUCCESS (0 secs / 0.079 secs)

2019年7月10日 星期三

在Azure Pipeline 裡使用 docker build 執行 asp.net core 的 unit test 後,把測試結果跟code coverage 上傳到組建裡

在Azure Pipeline 裡使用 docker build 執行 asp.net core 的 unit test 後,把測試結果跟code coverage 上傳到組建裡

在Pipeline建置時,如果要看到測試結果跟code coverage , 需要在Pipeline中建置,才能在azure devops server 的介面中看到測試結果跟程式碼含蓋範圍。像這樣
enter image description here

但是如果要把unit test 放在 docker 裡,在 multi stage builds 的過程之中執行unit test,在預設的狀況下,測試結果會在某一個build stage中,就不能使用pipeline的task來上傳測試結果。

解決方法

在docker 建置過程中做unit test , 另外產生測試結果。等到docker build 結束之後,用powershell把測試結果copy出來,然後再分別用 pipeline 的task 上傳測試結果及程式碼含蓋範圍。 就像下圖的步驟 3,4,5一樣。

enter image description here

Build docker image

因為我有好幾個測試專案,預設會產生好幾個code coverage的report 。 所以在執行dotnet test 有一些特殊的參數來讓這些report 合併成一個。

第一個是 CoverletOutputFormat=“json%2ccobertura” ,用來指定產出report的格式,注意這個 %2c 是分號 ; 的意思。 但是如果直接寫;在docker build 時會有powershell的錯誤。弄了很久才找到解法,改成%2C (是ascii code ; 的意思)就可以過了。

第二個是MergeWith=/testresults/coverage/coverage.json 。用MergeWith 來指定多個 code coverage 的report 跟誰合併,要指定report的位置在個目錄。

另外,在dockerfile 裡我有下了一個 label 叫 test=true ,這個是用來讓docker build 完之後還可以找到暫存的stage ,從裡面把我們的測試報告copy出來。

底下是我部份的dockerfile , 我的web.sln 裡面有3個程式專案及4個測試專案。

RUN dotnet restore web.sln
RUN dotnet build web.csproj -c Release -o /app/out 

# unit test
#先下一個label 
LABEL test=true
# 安裝dotnet-reportgenerator-globaltool 用來產生cobertura 格式的 code coverage report
RUN dotnet tool install dotnet-reportgenerator-globaltool --version 4.2.2 --tool-path /tools  

# %2c 是 ;(分號) 很重要!! 很重要!! 因為直接用; 會出錯。
RUN dotnet test --results-directory /testresults --logger:trx /p:CollectCoverage=true /p:CoverletOutputFormat="json%2ccobertura" /p:MergeWith=/testresults/coverage/coverage.json /p:Exclude="[xunit.*]*" /p:CoverletOutput=/testresults/coverage/ web.sln

RUN /tools/reportgenerator.exe "-reports:/testresults/coverage/coverage.cobertura.xml"  "-targetdir:/testresults/coverage/reports"  "-reporttypes:HTMLInline`;HTMLChart"

用powershell 取得測試結果

enter image description here

script說明

  1. 先找到下label=test的 docker id, 預設會新的在最上面,所以抓第 0 個
  2. 用找到的id 建立一個臨時的container
  3. 從container把測試結果copy 出來
  4. 這個臨時的container就可以移除惹
$id=$($(docker images --filter "label=test=true" -q)[0]);
docker create --name testcontainer $id;
docker cp testcontainer:/testresults ./;
docker rm testcontainer;

發行測試結果

enter image description here
測試結果格式選 VSTest
搜尋資料夾要選對,我的例子是 testresults

發行程式碼涵蓋範圍結果

enter image description here
程式碼涵蓋範圍工具 選cobertura
摘要檔案 跟 報告目錄 路徑要設對


如果都成功了,就可以在組建的摘要裡看到測試結果了,就像第一張圖一樣。 也可以在測試的tab看到詳細的結果
enter image description here

同場加映

看完上面的,有沒有感到奇怪,為什麼沒有code coverage 的詳細資料。
因為這是 Azure Devops Server 2019 目前版本的bug,但是線上版的Azure devops 是有詳細資料的。

在 2019/5/22 己經有人回報給 visual studio team了 ,MS也有回覆下個release會修好。

Code coverage tab missing in Azure DevOps Server

另外

前端(angular) 的unit test 也可以用類似的方法,產出測試報告跟上傳到devops server上,有空再來分享另一段血淚史。