しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

GitHub Actions / Azure Pipelines で Pull Request に特定のラベルが付けられた時だけ処理を行う

個人的によく使っていて時々 Pull Request も投げている Durable Functions の開発リポジトリでは、全ての Pull Request に対しては基本的なテストのみ実行し、full-ci というラベルが付いた時のみ全てのテストを実行するようになっています。

実際に以前投げた Pull Request は影響範囲の広い修正だったので、full-ci ラベルが付けられてテストを全て実行し、パスしたのを確認してマージされました。

理想的には全ての Pull Request で全てのテストを実行するべきなのでしょうが、テストに関しては時間的な制限もあって難しいので、この運用は個人的にかなり良い感じだと思っていました。

常に全てのテストを実行する必要がないことは開発中していて気が付きますし、テストに時間がかかってマージやリリースが遅れ始めるとテストが邪魔扱いされかねません。そういう事態を防ぐためにも、レビュアーが全てのテストを実行させるか判断して、ワーカーという有限なリソースを適切に割り振れるのも良いです。

直近で同じようなフローを組み込みたい案件があったため、GitHub Actions と Azure Pipelines の両方で同じ動作を実現する Workflow を書いてみました。結論としては GitHub Actions なら非常に簡単でした。

GitHub Actions

まずは GitHub Actions を使った方法ですが、流石 GitHub という感じで最初からいろいろな情報が取れるようになっていました。PR に特定のラベルが含まれているかの判定は以下の回答の通りに簡単に書けます。

この判定を Workflow 内の if の条件式として渡せば完成です。簡単すぎました。

on:
  pull_request:
    types: [opened, synchronize, labeled]
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2

    - name: Build projects
      run: dotnet build -c Release

    - name: Test projects
      run: dotnet test -c Release
      if: contains(github.event.pull_request.labels.*.name, 'full-ci')

ラベルは自由に設定出来るので、柔軟性が高くて良い感じですね。特に GitHub Actions では Pull Request にラベルが付けられたイベントも用意されているので、本当にラベルを付けるだけで済むのが良いです。

今回はサンプルとして単体テストの実行をラベルの有無で実行するかを判定しています。まずはラベル無しで Pull Request をそのまま作成してみた例です。

f:id:shiba-yan:20210112015751p:plain

ラベルが付いていないので単体テストはスキップされているのが確認できます。

この状態で full-ci ラベルを付けると、即座に Workflow が実行されます。ラベルのイベントを拾うようにしているので、Workflow の手動での再実行は必要ありません。

f:id:shiba-yan:20210112015801p:plain

結果を見ると意図したとおり単体テストが実行されていることが確認できます。

f:id:shiba-yan:20210112015809p:plain

この後にコミットを追加しても、既にラベルが付いているので常に単体テストが実行されるようになります。テストに時間がかかる場合にはレビューのタイミングで付けるのがバランスが良いでしょう。

Azure Pipelines

GitHub Actions の場合は簡単に実現できましたが、Azure Pipelines の場合は Pull Request のラベルは変数で渡される仕組みになっていないので、何とか取得する必要があります。

Azure Repos でも一応 GitHub のようにラベルは付けられるようになっているので、REST API を Pipeline 内で実行して取得するようにします。以下の API で PR に付いているラベル一覧を取得できます。

Azure Pipelines にも GitHub Actions のように API 実行のためのアクセストークンは用意されているので、REST API を実行するのは比較的簡単に行えます。

この辺りの処理は Template を使って分離することにしました。これで再利用性も高まります。

parameters:
- name: name
  type: string
- name: labelName
  type: string

steps:
- powershell: |
    $result = Invoke-RestMethod `
      -Method GET `
      -Uri $(System.CollectionUri)_apis/git/repositories/$(Build.Repository.ID)/pullRequests/$(System.PullRequest.PullRequestId)/labels?api-version=6.0-preview.1 `
      -Headers @{
        "Authorization" = "Bearer $(System.AccessToken)";
      }
    echo "##vso[task.setvariable variable=exists;isoutput=true]$($result.value.name -contains '${{ parameters.labelName }}')"
  name: ${{ parameters.name }}
  displayName: Check ${{ parameters.labelName }} PR label

Azure Pipelines は変数周りが非常に貧弱で配列は使えないため Logging command を使って、パラメータで指定されたラベルが存在したかどうかを変数として返します。

この Template を実際に使う例が以下の Pipeline 定義になります。GitHub Actions の時とは異なり 1 つ処理が増えてしまうのが残念ですが、これで同じ処理が実現できます。

trigger:
- main

pool:
  vmImage: 'ubuntu-latest'

steps:
- script: dotnet build -c Release
  displayName: Build projects

- template: check-pr-label.yml
  parameters:
    name: full_ci
    labelName: 'Full CI'

- script: dotnet test -c Release
  condition: eq(variables['full_ci.exists'], true)
  displayName: Test projects

単体テストを実行するスクリプトの condition で、ラベルが存在したかどうか条件式を指定しています。冗長さは否めませんが色々な方法を検討した結果、この方法に落ち着きました。

同じように Azure Repos でラベルを付けずに Pull Request を作成すると、単体テストはスキップされます。

f:id:shiba-yan:20210112015829p:plain

次に Pull Request にラベルを付けるわけですが、REST API ではラベルと呼ばれているのに Pull Request の画面では Tags という扱いになっているため、若干混乱しそうです。

f:id:shiba-yan:20210112015840p:plain

GitHub Actions ではラベルを付けると自動で Workflow が実行されましたが、Azure Pipelines にはラベルを付けたというイベントは用意されていないため、手動で Pipeline を再実行するかコミットを追加するしかありません。Pull Request の画面から Re-queue するのが楽です。

f:id:shiba-yan:20210112015848p:plain

これでラベルを付けた後に実行される Pipeline では単体テストが常に実行されるようになりました。

Azure Pipelines の場合は GitHub Actions のように実行時間での課金ではなく、ワーカーの数による課金なので 1 つの Pipeline の実行に時間がかかると、他の処理が止まりがちになります。必要なタイミングにだけ動かすのが早く回すコツになりそうです。