Azure Functions を使用してカスタム ブランチ ポリシーを作成する
Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019
pull request (PR) ワークフローを使用すると、開発者は、自動化されたツールからだけでなくピアからもコードに関するフィードバックを得ることができます。 サードパーティのツールとサービスは、PR Status API を使用して PR ワークフローに参加できます。 この記事では、Azure Functions を使用してカスタム ブランチ ポリシーを作成し、Azure DevOps Services Git リポジトリ内の PR を検証するプロセスについて説明します。 Azure Functions を使用すると、特にワークロードが増加したときに、サーバーのプロビジョニングやメンテナンスについて心配する必要がなくなります。 Azure Functions は、高い信頼性とセキュリティを備えたフル マネージド コンピューティング プラットフォームを提供します。
PR 状態の詳細については、「プル要求状態でプル要求ワークフローをカスタマイズして拡張する」を参照してください。
前提条件
Git リポジトリを使用する Azure DevOps の組織。 組織がない場合は、サインアップして、無制限の無料プライベート Git リポジトリでコードをアップロードして共有します。
Azure Repos イベントをリッスンする基本的な Azure 関数を作成する
最初の Azure 関数の作成に関するドキュメントに従って、単純な関数を作成します。 サンプルのコードをこのように変更します。
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
try
{
log.Info("Service Hook Received.");
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
log.Info("Data Received: " + data.ToString());
// Get the pull request object from the service hooks payload
dynamic jObject = JsonConvert.DeserializeObject(data.ToString());
// Get the pull request id
int pullRequestId;
if (!Int32.TryParse(jObject.resource.pullRequestId.ToString(), out pullRequestId))
{
log.Info("Failed to parse the pull request id from the service hooks payload.");
};
// Get the pull request title
string pullRequestTitle = jObject.resource.title;
log.Info("Service Hook Received for PR: " + pullRequestId + " " + pullRequestTitle);
return req.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
log.Info(ex.ToString());
return req.CreateResponse(HttpStatusCode.InternalServerError);
}
}
PR イベントのサービス フックを構成する
サービス フックは、特定のイベントが発生したときに外部サービスに警告できる Azure DevOps Services の機能です。 このサンプルでは、PR イベントのサービス フックを設定する必要があります。pull request が変更されると、Azure 関数に通知されます。 pull request が変更されたときに POST
要求を受信するには、サービス フックに Azure 関数 URL を指定する必要があります。
このサンプルでは、2 つのサービス フックを構成する必要があります。 1 つ目は [Pull request が作成されました] イベント用で、2 つ目は [Pull request の更新完了] イベント用です。
Azure 関数ビューで [関数の URL の取得] をクリックして、Azure portal から関数 URL を取得し、URL をコピーします。
Azure DevOps でプロジェクトを参照します (例:
https://dev.azure.com/<your organization>/<your project name>
)ナビゲーション メニューから、歯車にホバーして [サービス フック] を選択します。
これが最初のサービス フックである場合は、[サブスクリプションの作成] を選択します。
他のサービス フックが既に構成されている場合は、緑色のプラス
(+)
を選択して、新しいサービス フック サブスクリプションを作成します。[新しいサービス フック サブスクリプション] ダイアログで、サービスの一覧から [Web フック] を選択し、[次へ] を選択します。
イベント トリガーの一覧から [Pull request が作成されました] を選択し、[次へ] を選択します。
[アクション] ページで、手順 1 でコピーした URL を [URL] ボックスに入力します。 [テスト] を選択して、テスト イベントをサーバーに送信します。
Azure 関数ログ ウィンドウに、
200 OK
を返した受信POST
が表示されます。これは、関数がサービス フック イベントを受信したことを示します。HTTP Requests ------------- POST / 200 OK
[テスト通知] ウィンドウで、[応答] タブを選択して、サーバーからの応答の詳細を表示します。 サーバーからの応答が表示されます。
[テスト通知] ウィンドウを閉じ、[終了] を選択してサービス フックを作成します。
手順 2 から 8 を再度実行しますが、今回は [Pull request の更新完了] イベントを構成します。
重要
必ず前述の手順を 2 回実行し、[Pull request が作成されました] および [Pull request の更新完了] の両方のイベントのサービス フックを作成してください。
pull request を作成して、Azure 関数が通知を受信することを検証します。
PR に状態を投稿する
新しい PR が作成されたときにサーバーがサービス フック イベントを受信できるようになったので、PR に状態をポストバックするように更新します。 サービス フックによって投稿された JSON ペイロードを使用して、PR に設定する状態を決定できます。
Azure 関数のコードを次の例のように更新します。
必ず、自分の組織名、プロジェクト名、リポジトリ名、PAT トークンでコードを更新してください。 PR 状態を変更するアクセス許可を得るには、PAT に vso.code_status スコープが必要になります。これは、[個人用アクセス トークンの作成] ページで [コード (状態)] スコープを選択することで付与できます。
重要
このサンプル コードでは、サンプルを簡略化するためにコード内に PAT を保存しています。 KeyVault にシークレットを保存し、そこから取得することをお勧めします。
このサンプルでは、PR タイトルを調べて、ユーザーがタイトルに WIP を追加することで PR が進行中の作業であることを示しているかどうかを確認します。 その場合、このサンプル コードでは PR にポストバックされた状態を変更します。 Azure 関数のコードを次のコードに置き換えて、PR にポストバックされた状態の更新を実装します。
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;
private static string organizationName = "[Organization Name]"; // Organization name
private static string projectName = "[Project Name]"; // Project name
private static string repositoryName = "[Repo Name]"; // Repository name
/*
This is here just to simplify the sample, it is recommended to store
secrets in KeyVault and retrieve them from there.
*/
private static string pat = "[PAT TOKEN]";
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
try
{
log.Info("Service Hook Received.");
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
log.Info("Data Received: " + data.ToString());
// Get the pull request object from the service hooks payload
dynamic jObject = JsonConvert.DeserializeObject(data.ToString());
// Get the pull request id
int pullRequestId;
if (!Int32.TryParse(jObject.resource.pullRequestId.ToString(), out pullRequestId))
{
log.Info("Failed to parse the pull request id from the service hooks payload.");
};
// Get the pull request title
string pullRequestTitle = jObject.resource.title;
log.Info("Service Hook Received for PR: " + pullRequestId + " " + pullRequestTitle);
PostStatusOnPullRequest(pullRequestId, ComputeStatus(pullRequestTitle));
return req.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
log.Info(ex.ToString());
return req.CreateResponse(HttpStatusCode.InternalServerError);
}
}
private static void PostStatusOnPullRequest(int pullRequestId, string status)
{
string Url = string.Format(
@"https://dev.azure.com/{0}/{1}/_apis/git/repositories/{2}/pullrequests/{3}/statuses?api-version=4.1",
organizationName,
projectName,
repositoryName,
pullRequestId);
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(
ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", "", pat))));
var method = new HttpMethod("POST");
var request = new HttpRequestMessage(method, Url)
{
Content = new StringContent(status, Encoding.UTF8, "application/json")
};
using (HttpResponseMessage response = client.SendAsync(request).Result)
{
response.EnsureSuccessStatusCode();
}
}
}
private static string ComputeStatus(string pullRequestTitle)
{
string state = "succeeded";
string description = "Ready for review";
if (pullRequestTitle.ToLower().Contains("wip"))
{
state = "pending";
description = "Work in progress";
}
return JsonConvert.SerializeObject(
new
{
State = state,
Description = description,
TargetUrl = "https://visualstudio.microsoft.com",
Context = new
{
Name = "PullRequest-WIT-App",
Genre = "pr-azure-function-ci"
}
});
}
状態サーバーをテストする新しい PR を作成する
サーバーが実行され、サービス フック通知をリッスンしている状態になったので、pull request を作成してテストします。
ファイル ビューから開始します。 リポジトリ内の readme.md ファイル (または readme.md がない場合は他のファイル) を編集します。
編集を行い、リポジトリに変更をコミットします。
次の手順で PR を作成できるように、必ず新しいブランチに変更をコミットしてください。
[Pull request の作成] リンクを選択します。
タイトルに WIP を追加して、アプリの機能をテストします。 [作成] を選択して、PR を作成します。
PR が作成されると、ペイロードで指定された URL にリンクする [進行中の作業] エントリを含む [状態] セクションが表示されます。
PR タイトルを更新し、WIP テキストを削除すると、状態が [進行中の作業] から [Ready for review](レビュー準備完了) に変わることに注意してください。
次の手順
- この記事では、サービス フックを介して PR イベントをリッスンし、status API を使用してステータス メッセージを投稿できるサーバーレス Azure 関数を作成する方法の基本について説明しました。 pull request status API の詳細については、REST API ドキュメントを参照してください。
- 外部サービスのブランチ ポリシーを構成します。