以编程方式创建服务挂钩订阅

Azure DevOps Services |Azure DevOps Server |Azure DevOps Server 2022 |Azure DevOps Server 2020

当 Azure DevOps 项目中发生特定事件时,可以使用订阅来对外部或消费者服务执行某个动作。 例如,订阅可以在构建失败时通知你的服务。

若要以编程方式创建订阅,可以使用 订阅 REST API。 本文提供了用于创建订阅的示例请求和示例代码。

先决条件

类别 要求
项目访问权限 项目成员
数据 - 项目 ID。 使用 Project REST API 获取项目 ID。
- 事件 ID 和设置。 请参阅 服务挂钩事件
- 消费者和操作 ID 及设置。 请参阅 服务挂钩使用者

支持事件

Azure DevOps 为许多触发器事件提供支持。 示例包括以下事件:

  • 构建已完成
  • 推送的代码(适用于 Git 项目)
  • 已创建或更新拉取请求(适用于 Git 项目)
  • 签入的代码(适用于 Team Foundation 版本控制项目)
  • 已创建、更新、删除、还原或批注的工作项

若要控制哪些事件触发动作,可以为订阅配置过滤器。 例如,可以根据构建状态筛选构建完成事件。

创建请求

创建订阅时,可以使用 HTTP POST 请求的正文来指定项目 ID、事件、使用者、作和相关设置。

可以使用以下请求为构建完成事件创建订阅。 在这个示例中,当 WebSite.CI 构建失败时,订阅会向 https://myservice/event 发送 POST 请求。

请求

{
    "publisherId": "tfs",
    "eventType": "build.complete",
    "resourceVersion": "1.0",
    "consumerId": "webHooks",
    "consumerActionId": "httpRequest",
    "publisherInputs": {
        "buildStatus": "failed",
        "definitionName": "WebSite.CI",
        "projectId": "11bb11bb-cc22-dd33-ee44-55ff55ff55ff",
    },
    "consumerInputs": {
        "url": " https://myservice/event"
    },
}

强烈建议使用安全 HTTPS URL 来保护 JSON 对象中的专用数据。

响应

创建订阅的请求将生成类似于以下响应的响应:

{
    "id": "aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e",
    "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/hooks/subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e",
    "publisherId": "tfs",
    "eventType": "build.complete",
    "resourceVersion": "1.0",
    "consumerId": "webHooks",
    "consumerActionId": "httpRequest",
    "createdBy": {
        "id": "22cc22cc-dd33-ee44-ff55-66aa66aa66aa"
    },
    "createdDate": "2014-03-28T16:10:06.523Z",
    "modifiedBy": {
        "id": "22cc22cc-dd33-ee44-ff55-66aa66aa66aa"
    },
    "modifiedDate": "2014-04-25T18:15:26.053Z",
    "publisherInputs": {
        "buildStatus": "failed",
        "definitionName": "WebSite.CI",
        "hostId": "d3d3d3d3-eeee-ffff-aaaa-b4b4b4b4b4b4",
        "projectId": "11bb11bb-cc22-dd33-ee44-55ff55ff55ff",
        "tfsSubscriptionId": "ffff5f5f-aa6a-bb7b-cc8c-dddddd9d9d9d"
    },
    "consumerInputs": {
        "url": "http://myservice/event"
    }
}

如果订阅请求失败,则会收到 HTTP 响应代码 400,其中包含具有更多详细信息的消息。

事件发生时会发生什么情况?

当事件发生时,将评估项目中所有已启用的订阅项。 然后,对所有匹配的订阅执行消费者操作。

资源版本(高级)

当 API 处于预览状态时,资源版本控制适用。 对于大多数方案,将 1.0 指定为资源版本是最安全的路由。

发送到某些使用者的事件有效负载包括主题资源的 JSON 表示形式。 例如,发送到 Webhook、Azure 服务总线和 Azure 存储的有效负载包括有关生成或工作项的信息。 此资源的表示形式可以具有各种形式或版本。

您可以通过订阅中的 resourceVersion 字段指定要发送到消费者服务的资源版本。

资源版本与 API 版本相同。 如果未指定资源版本,则使用最新版本 latest released。 为了帮助确保一段时间内的事件有效负载一致,请始终指定资源版本。

常见问题解答

问:是否有服务可以手动订阅?

答:是的。 有关可从项目管理页订阅的服务的详细信息,请参阅 “与服务挂钩集成”。

问:是否有可用于创建订阅的 C# 库?

答:否,但下面是帮助你入门的示例。 若要对 Azure DevOps 进行身份验证,以下代码使用存储在 Azure Key Vault 中的个人访问令牌(PAT)。 在生产环境中,使用更安全的身份验证方法。 有关详细信息,请参阅 选择正确的身份验证机制

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.ServiceHooks.WebApi;
using Microsoft.VisualStudio.Services.WebApi;

namespace CreateServiceHookSubscription
{
    internal class Program
    {
        // Create a service hook subscription to send a message to an Azure Service Bus queue when code is pushed to a Git repository.

        static async Task Main(string[] args)
        {
            // Get the secrets from the key vault.
            string keyVaultURI = "https://<key-vault-name>.vault.azure.net/";
            var secretClient = new SecretClient(new Uri(keyVaultURI), new DefaultAzureCredential());
            string personalAccessTokenSecretName = "<personal-access-token-secret-name>";
            string serviceBusConnectionStringSecretName = "<Service-Bus-connection-string-secret-name>";
            KeyVaultSecret personalAccessTokenSecret = await secretClient.GetSecretAsync(personalAccessTokenSecretName);
            KeyVaultSecret serviceBusConnectionStringSecret = await secretClient.GetSecretAsync(serviceBusConnectionStringSecretName);

            // Set up the connection parameters for Azure DevOps.
            var azureDevOpsOrganizationURL = new Uri("https://dev.azure.com/<Azure-DevOps-organization-name>/");
            string azureDevOpsTeamProjectID = "<Azure-DevOps-team-project-ID>";
            string azureDevOpsPersonalAccessToken = personalAccessTokenSecret.Value;

            // Set up the event parameters.
            string eventPublisherID = "tfs";
            string eventID = "git.push";
            string eventDescription = "Any stage in any release";
            string resourceVersion = "1.0";

            // Set up the consumer parameters.
            string consumerID = "azureServiceBus";
            string consumerActionID = "serviceBusQueueSend";
            string serviceBusNamespace = "<Service-Bus-namespace>";
            string serviceBusQueueName = "<Service-Bus-queue-name>";
            string consumerActionDescription = $"Send a message to the Service Bus {serviceBusQueueName} queue in the {serviceBusNamespace} namespace.";
            string serviceBusConnectionString = serviceBusConnectionStringSecret.Value;

            // Configure the subscription.
            var subscription = new Subscription()
            {
                PublisherId = eventPublisherID,
                PublisherInputs = new Dictionary<string, string>
                {
                    ["projectId"] = azureDevOpsTeamProjectID
                },
                EventType = eventID,
                EventDescription = eventDescription,
                ResourceVersion = resourceVersion,
                ActionDescription = consumerActionDescription,
                ConsumerActionId = consumerActionID,
                ConsumerId = consumerID,
                ConsumerInputs = new Dictionary<string, string>
                {
                    ["connectionString"] = serviceBusConnectionString,
                    ["queueName"] = serviceBusQueueName
                }
            };

            // Connect to the Azure DevOps organization and get a service hook client.
            var azureDevOpsCredentials = new VssBasicCredential(azureDevOpsPersonalAccessToken, string.Empty);
            var azureDevOpsConnection = new VssConnection(azureDevOpsOrganizationURL, azureDevOpsCredentials);
            var serviceHookClient = azureDevOpsConnection.GetClient<ServiceHooksPublisherHttpClient>();

            // Create the subscription.
            var createdSubscription = await serviceHookClient.CreateSubscriptionAsync(subscription);
            Console.WriteLine($"A subscription was created that has ID {createdSubscription.Id}.");
        }
    }
}