Azure Pipelines は YAML を使ってスクリプトを書くだけではなく、予め用意されたタスクを使って面倒な処理をシンプルに書くことが出来ますが、地味にはまるポイントも多いのでよく使う定義をメモしておきます。
YAML Schema 読めば大体は理解できるはずですが、多少ドキュメントが古いタスクがあったりします。
この間、ディレクトリを指す変数はどれを使った方が良いか調べたエントリもあるので、こっちも一緒に参照して貰えるとよりスムーズにビルドパイプラインを定義できると思います。
タスクで使われるベースとなるディレクトリについても、メモがてら調べて追記しておきました。これで悩まずにパス周りの定義を書くことが出来そうです。
他にも見つけたら追記するかもしれません。とりあえず自分がよく使う範囲で書いています。
タスクで共通な部分
ファイルのパターンマッチ
内部では minimatch を使っているみたいなので、ドキュメント曰く多くのパターンが使えるはずです。
よく使われるのは **
と *
ぐらいかと思います。!
を付けると否定になります。
# 1 行で指定するパターン searchPatternPack: '**/*.csproj;-:**/*.Tests.csproj' # 複数行で指定するパターン projects: | **/*.csproj !**/*.Tests.csproj
1 行で指定するパターンは内部でレガシー扱いになってるようだったので、先行きが怪しいです。
ツールバージョンの指定
一部のタスクではバージョンを柔軟に指定できます。以下のパターンを知っておけば大体困らないです。
# .NET Core の例 version: '2.x' version: '2.1.x' version: '3.0.100-preview7-012821' # Node / Python / Ruby / NuGet の例 # タスクによって version と versionSpec と差があるので注意 version: '10.6.x' versionSpec: '>= 2.4'
微妙にプロパティ名が統一されていないのが不満です。
GitHub Tag トリガーを判別
Azure Pipelines の各 Stage / Job は condition
で実行条件を指定できますが、GitHub の Tag が打たれた時だけ実行という条件を組むのは少し面倒です。
最近は変数を使ってタグかどうかを判別するようにすることが多いです。
trigger: branches: include: - master tags: include: - v* variables: isGitHubTag: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/v') }}
この時 isGitHubTag
は true / false となるので、後は condition
で参照します。
condition: and(succeeded(), eq(variables['isGitHubTag'], 'true'))
カスタム関数が書ければもっと上手く書けるはずですが、今はこれで妥協しておきます。
checkout 系
checkout を無効化
steps: - checkout: none
submodule を同時に checkout する
パッと見 submodule
は true / false かと思いきや、recursive
も受け付けるので注意。
# 直下の submodule だけ checkout する場合 steps: - checkout: self submodule: true # 再帰的に submodule を checkout する場合 steps: - checkout: self submodule: recursive
checkout する履歴を減らす
履歴が大きなリポジトリをチェックアウトする際に時間がかかるのを改善出来ます。
steps: - checkout: self fetchDepth: 1
ツールインストール系
.NET Core をバージョン指定でインストール
インストールするバージョンを変数にして strategy
/ matrix
で使うことも出来ます。
steps: - task: UseDotNet@2 inputs: packageType: 'sdk' version: '2.2.x' displayName: 'Install .NET Core SDK 2.2.x'
steps: - task: UseDotNet@2 inputs: packageType: 'sdk' version: '3.0.100-preview7-012821' displayName: 'Install .NET Core SDK 3.0.100-preview7-012821'
Node.js をバージョン指定でインストール
.NET Core や Node.js に限らず、言語系は Use***
というタスクに変更されました。
steps: - task: UseNode@1 inputs: version: '10.x' displayName: 'Install Node.js 10.x'
NuGet CLI をバージョン指定でインストール
NuGet CLI のデフォルトは 4.1.0 と古いので、新しいバージョンを入れておいた方が良いです。
steps: - task: NuGetToolInstaller@1 inputs: versionSpec: '5.0.x' displayName: 'Install NuGet CLI 5.0.x'
.NET Core CLI 系
アプリケーションを発行
ビルド設定と出力先ディレクトリは arguments
で指定する必要があります。
ASP.NET Core の場合は publishWebProjects
を設定しなくても問題ないですが、Azure Functions の場合は false
を設定する必要があります。
variables: BuildConfiguration: 'Release' steps: - task: DotNetCoreCLI@2 inputs: command: 'publish' publishWebProjects: false projects: '**/*.csproj' arguments: '-c $(BuildConfiguration)-o $(Build.SourcesDirectory)/dist'
普通に dotnet publish
で実行するのとは違って、発行するプロジェクトをワイルドカードで指定したり、発行後のディレクトリを自動で zip にしたりと便利な機能が使えます。
NuGet パッケージを作成
outputDir
のベースとなるディレクトリは $(Build.SourcesDirectory)
です。相対パスが楽です。
variables: BuildConfiguration: 'Release' steps: - task: DotNetCoreCLI@2 displayName: 'dotnet pack' inputs: command: pack searchPatternPack: '**/*.csproj;-:**/*.Tests.csproj' outputDir: 'dist'
.NET Core CLI ではなく NuGet CLI を使って作成する場合でも、あまり変わらないです。以下の例では nuspec
から NuGet パッケージを作成しています。
steps: - task: NuGetCommand@2 displayName: 'nuget pack' inputs: command: 'pack' packagesToPack: '.\Demo.nuspec' packDestination: 'dist' basePath: '.\build'
この時の packDestination
などのベースとなるディレクトリは $(Build.SourcesDirectory)
です。
NuGet パッケージの公開
.NET Core CLI と NuGet CLI の両方でパッケージの公開を行えます。機能的にも差はないです。パッケージを探すパターンのベースとなるディレクトリは $(Build.SourcesDirectory)
です。
steps: - task: DotNetCoreCLI@2 displayName: 'dotnet nuget push' inputs: command: push searchPatternPush: '**/*.nupkg' nugetFeedType: external externalEndPoint: NuGet
steps: - task: NuGetCommand@2 displayName: 'nuget push' inputs: command: push packagesToPush: '**/*.nupkg' nuGetFeedType: external publishFeedCredentials: NuGet
多少 NuGet CLI の方が古い印象を受けますが、やっていることは同じなので好きな方を使えば良いです。
コンテナー系
Container jobs を使う
Windows と Ubuntu の場合は Docker Image の内部でタスクを実行出来ます。
pool: vmImage: 'ubuntu-latest' container: 'mcr.microsoft.com/dotnet/core/sdk:2.2'
使える Docker Image には多少の制限があるので、何でも使えるわけではないです。
具体的には Bash と glibc ベースで、Node.js が実行可能かつ ENTRYPOINT
が無いイメージじゃないとダメなようです。なので Alpine ベースだと動きませんでした。
ファイル操作系
ファイルコピー
targetFolder
のベースとなるディレクトリは $(Build.SourcesDirectory)
です。
steps: - task: CopyFiles@2 inputs: contents: | dist/**/* package*.json targetFolder: 'dist'
パターンは除外も書けるのでなかなか強力です。VM Image に依存しないコピーとして便利に使えます。
アーカイブ作成
rootFolderOrFile
と archiveFile
のベースとなるディレクトリは $(Build.SourcesDirectory)
です。
steps: - task: ArchiveFiles@2 inputs: rootFolderOrFile: 'dist' includeRootFolder: false archiveType: 'zip' archiveFile: '$(Build.BuildNumber).zip'
Pipeline Artifacts のことを考えると、全体的に $(Build.SourcesDirectory)
へ統一した方が楽です。
Artifacts 系
Pipeline Artifacts へプッシュ
ベースとなるディレクトリは $(Build.SourcesDirectory)
です。artifact
を省略した場合は自動で "Stage 名 + Job 名" という名前が付けられます。
steps: - publish: dist artifact: nupkg
Pipeline Artifacts からダウンロード
ベースとなるディレクトリは $(Pipeline.Workspace)
となります。artifact
を省略した場合は全ての Artifacts をダウンロードします。
steps: - download: current artifact: nupkg
ちなみに Deployment jobs を使うと自動で行われるので便利です。
デプロイ系
Azure Web App にデプロイ
Windows の Web App へのデプロイ時には Run From Package を使うようにした方が、デプロイが Atomic なので安定します。パッケージは $(Pipeline.Workspace)
から拾ってくるケースが大半だと思います。
steps: - task: AzureWebApp@1 inputs: azureSubscription: 'AzureRMConnection' appType: 'webApp' appName: 'deploy-test' package: '$(Pipeline.Workspace)/**/*.zip' deploymentMethod: 'runFromPackage'
Deployment Slot に対してデプロイする場合は slotName
でデプロイ先スロットを指定すれば良いです。
ドキュメントなどではリソースグループ名が必要っぽく書いてますが、実際には不要でした。
steps: - task: AzureWebApp@1 inputs: azureSubscription: 'AzureRMConnection' appType: 'webApp' appName: 'deploy-test' slotName: 'staging' package: '$(Pipeline.Workspace)/**/*.zip' deploymentMethod: 'runFromPackage'
リソースグループ名の設定は地味に面倒なので、アプリ名とスロット名だけでデプロイ出来るのは楽です。
Azure Function にデプロイ
Web App へのデプロイとほとんど同じですが、タスクと appType
が異なっています。
steps: - task: AzureFunctionApp@1 inputs: azureSubscription: 'AzureRMConnection' appType: 'functionApp' appName: 'deploy-test' package: '$(Pipeline.Workspace)/**/*.zip' deploymentMethod: 'runFromPackage'
Azure Function の場合も Run From Package を使うことで、Consumption での Cold Start にかかる時間を削減できますし、デプロイ自体もやはり安定するのでもはや必須です。