DotNetCurry Logo

Service Hooks Integration with VSTS and Azure Function

Posted by: Gouri Sohoni , on 2/9/2017, in Category Visual Studio, VSTS & TFS
Views: 2373
Abstract: Create service hook for any VSTS project by using a wizard as well as programmatically and use this web hook with an azure function.

Service Hooks enable us to perform certain actions based on an event happening in a project on Visual Studio Team Services (VSTS). The event can be about sending notifications when a build completes. It can be code related events or work item related events.

 

I have written a series of articles about creating, publishing and authoring VSTS extension.

In this article, I am going to describe how to handle events raised by VSTS by implementing a Service Hook for that.

Service Hooks Overview

Service Hooks enable us to perform certain actions based on an event happening in any project on Visual Studio Team Services (VSTS). The event can be about sending notifications when a build completes. It can be code related events or work item related events. In our case for TaskO, we needed to create children work items of the type task the moment any requirement, user story or product backlog item is created in any project. So service hook is like an event handler that executes some code hosted at a remote place.

Service hooks have typically 3 parts: publishers, subscribers and consumers.

Publishers are nothing but a set of events. Visual Studio Team Services (VSTS) is a publisher. To get a list of all publishers related to our VSTS account, we can send a GET request in the browser
https://.visualstudio.com/DefaultCollection/_apis/hooks/publishers?api-version=1.0

We get a json file which gives all the information about the various publishers with this VSTS account like github, Release management and TFS.

The request https://.visualstudio.com/DefaultCollection/_apis/hooks/publishers/tfs/eventTypes?api-version=1.0 gives a list of all the available events. As of today, I am getting 12 as count for these events. Following is an example for one such event

{
      "publisherId": "tfs",
      "scope": "project",
      "id": "workitem.deleted",
      "url": "https://< VSTS Account Name >.visualstudio.com/_apis/hooks/publishers/tfs/eventTypes/workitem.deleted",
      "name": "Work item deleted",
      "description": "Filter events to include only newly deleted work items.",
      "supportedResourceVersions": [ "1.0", "3.1-preview.3", "1.0-preview.2" ],
      "inputDescriptors": [
        {
          "id": "areaPath",
          "name": "Area path",
          "description": "Filter events to include only work items under the specified area path.",
          "type": null,
          "properties": null,
          "inputMode": "combo",
          "isConfidential": false,
          "useInDefaultDescription": false,
          "groupName": null,
          "valueHint": null,
          "validation": { "dataType": "string" },
          "values": {
            "defaultValue": "",
            "possibleValues": [
              {
                "value": "",
                "displayValue": "[Any]"
              }
            ],
            "isLimitedToPossibleValues": true,
            "isReadOnly": true
          },
          "hasDynamicValueInformation": true
        },
        {
          "id": "workItemType",
          "name": "Work item type",
          "description": "Filter events to include only work items of the specified type.",
          "type": null,
          "properties": null,
          "inputMode": "combo",
          "isConfidential": false,
          "useInDefaultDescription": false,
          "groupName": null,
          "valueHint": null,
          "validation": { "dataType": "string" },
          "values": {
            "defaultValue": "",
            "possibleValues": [
              {
                "value": "",
                "displayValue": "[Any]"
              }
            ],
            "isLimitedToPossibleValues": true,
            "isReadOnly": true
          },
          "hasDynamicValueInformation": true
        }
      ]
    },

Subscriptions are created based on the events. A list of subscriptions can be obtained by sending a request

https://.visualstudio.com/DefaultCollection/_apis/hooks/subscriptions/?api-version=1.0

Consumer is the external component or service which is targeted by subscription. A list of consumers can be received by a request

https://.visualstudio.com/DefaultCollection/_apis/hooks/consumers?api-version=1.0. As of today, I am getting a list of 20 consumers which include Bamboo, hipChat, Trello, vso, webHooks, zapier etc.

For build and release there are Jenkins, Bamboo, Stack etc. For Integration, we have Azure service bus, azure storage, web hooks and zapier.

We have used web hooks for out integration. Web Hooks provide a way to send a message in JSON format from an event to any service. We also need to provide which endpoint will be used - HTTP or HTTPS. You can find more information about all service hook related events in VSTS.

Service Hooks Pre-requisites

Some Pre-requisites are as follows:

  • VSTS account
    • One Team Project with some code and existing build definition
  • Access to Azure Portal to create azure function which will be executed as an event is triggered (in this case Build Completed)

Creating a Service Hook

Let us find out how to create a service hook and how it can be automatically called. This service hook in turn will call a function in azure. This function in azure should have the mode of type webhook and Webhook type as JSON as shown in the following figure.

azure-function-for-web-hook

Let us create an Azure function followed by service hook by using a wizard.

Create an Azure function

1. Select GenericWebHook-CSharp

2. The moment we select mode and web hook type as shown in previous figure, some default code is made available with the function. In this case, I deleted it and added my own code. You can use the default code for trial purpose.

3. The function looks as follows. I have taken the JSON string received and used it to give me two values. These are the trigger (manual, continuous integration) for build and the status of the build (pass, fail, partially succeeded or any). Observe the reference to Newtonsoft.Json. The information obtained is written to log function side entries when the build is completed.

Remember that this function is being executed as an event and will not be returning anything back.

#r "Newtonsoft.Json"
using System;
using System.Net;
using Newtonsoft.Json;

public static async void Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info($"Webhook was triggered!");
    HttpContent requestContent = req.Content;
    string jsonContent = requestContent.ReadAsStringAsync().Result;
    string resource = jsonContent.Substring(jsonContent.IndexOf("resource"));
    int ind = resource.IndexOf("reason") + 9;
    int ind2 = resource.IndexOf("status") - 3;
    string reason = resource.Substring(ind, ind2 - ind);
    ind = resource.IndexOf("status") + 9;
    ind2 = resource.IndexOf("drop") - 3;
    string status = resource.Substring(ind, ind2 - ind);
    log.Info("Reason for Build: " + reason);
    log.Info("Build Status: " + status);
}

Create Service Hook

Now that the Azure function is ready, let us create a Service hook. Please keep a build definition ready for a project which we will use as filter for the build completion event. This will only trigger event for that particular build definition and not for any other task.

I will show how to create subscription using wizard for build completed event as a part of web hook service.

We can create service hooks programmatically also. In fact, we have programmatically created service hooks for our product TaskO as we wanted to create it on the fly, the moment the user accesses the VSTS extension for any project. I will discuss how to programmatically create a service hook later in this article.

The Service Hooks publishers have a set of events which in this case will be build completed. We create a subscription based on the event and provide which action to take place at the completion of event (which in this case is the azure function we have written earlier).

1. In order to integrate the service with VSTS, we need to create a subscription. You can select a team project and select the settings page to view the tab for Service Hook.

service-hook-tab

2. You can click on the + sign to create a new subscription. The wizard will start and give a list of all the services for which integration is to be created.

service-hooks-subs

3. Click on Next and we will see a list of triggers when an event occurs.

service-hook-events

4. When we select Build Completed, it asks for a filter for build definition and build status. For this demo, I am selecting an existing build definition and going to keep status as [any].

build-event-filter

5. The next step is to provide the url of the azure function created earlier.

url-for-azure-function

6. Click on the Finish button to complete service hook subscription. The web hook can be seen in the service hooks tab.

web-hook

7. As this is event related, we do not have to invoke it, it will be automatically triggered the moment the specified build completes.

8. Let us trigger the build and wait till it completes execution. After the build is completed, refresh the service hook tab to find out that the web hook succeeded.

web-hook-succeded

9. We can find out if the azure function executed via this event. And voila! The function log entries specify that the function was triggered when the build was completed.

function-log

10. We can also view the history of web hooks to see how many times the event was triggered and if the web hook was completed successfully or not.

web-hook-history

11. Click on the Request tab and you will find out how JSON is sent to the azure function. I extracted my function values of status and trigger based on this response. In the Response, we will get Status Code: 200 if the web hook was successful.

Let us also find out how to create subscription programmatically.

Creating Service Hook Programmatically

I am going to create the same service hook programmatically (this time we will create it for any build definition).

For our product TaskO, we created another Azure Function which in turn programmatically creates the service hook, but for simplicity, I am going to show you the same by writing a Console application.

Pre-requisites for creating service hook programmatically:

  • VSTS account
    • One project with a build definition
  • Access token created for the VSTS account you are using (explained in previous article). You may also use Personal access Token (PAT) available from VSTS.
  • Azure function created earlier

1. We need to create a subscription for a particular Team Project. We have to provide HTTP POST request for the VSTS account with a particular event (build completed in this case), consumer and the action to be taken for the subscription.

2. Provide request https://.VisualStudio.com/DefaultCollection/_apis/projects?api-version=2.0 to get a list of projects available for the account. Note down the project id for which the service hook is to be created.

3. Create a new Console Application and given an appropriate name to it.

4. Right click on the project name in Visual Studio solution explorer and select Manage NuGet packages. Install Newtonsoft.

5. Add a class to it and write a method in it as CreateServiceHook.

6. Add using Newtonsoft.Json; in the class.

7. The CreateServiceHook method takes four parameters as VSTS account name, project name, project id (obtained in step 2) and access token (more on this in previous article on Authorization call back in Azure)

8. To create service hook, we have to create request for the HTTP client. This comprises of publisher id (tfs), event type (build completed), consumer id (web hooks), scope for the subscription (project), build status (if you want to filter) and the url for the azure function which will be considered as consumer.

The request looks as follows:

var request = new
{
   publisherId = "tfs",
   eventType = "build.complete",
   consumerId = "webHooks",
   consumerActionId = "httpRequest",
   scope = "project",
   publisherInputs = new {
         buildStatus = "",
         projectId = projId 
   },
   consumerInputs = new
   {
      url = "https< url for azure function >"
   }
};

9. Based on this request, we need to create a response and serialize the request.

var response = client.PostAsync("https://" + collName + ".visualstudio.com/DefaultCollection/_apis/hooks/subscriptions/?api-version=1.0",
 new tringContent(JsonConvert.SerializeObject(request).ToString(),
 Encoding.UTF8, "application/json")).Result;

10. The complete code looks as follows. For this method, we need to pass four parameters as already discussed.

public async static void CreateServiceHook(string collName, string projName, string projId, string AccessToken)
{
    try
    {
        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
           
           string url = "https://" + collName + ".visualstudio.com/DefaultCollection/_apis/projects/" + projName + "?includecapabilities=true&api-version=1.0";
            HttpRequestMessage req = new HttpRequestMessage(new HttpMethod("GET"), url);
            var response = client.SendAsync(req).Result;
            string contents = await response.Content.ReadAsStringAsync();

        }
        //create service hook
        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));

            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
            var request = new
            {
                publisherId = "tfs",
                eventType = "build.complete",
                consumerId = "webHooks",
                consumerActionId = "httpRequest",
                scope = "project",
                publisherInputs = new {
                    buildStatus = "",
                    projectId = projId 
                },
                consumerInputs = new
                {
                    url = "https://< url for azure function created earlier >"
                }

            };
            var response = client.PostAsync("https://" + collName + ".visualstudio.com/DefaultCollection/_apis/hooks/subscriptions/?api-version=1.0",
                new StringContent(JsonConvert.SerializeObject(request).ToString(),
                    Encoding.UTF8, "application/json"))
                    .Result;

            if (response.IsSuccessStatusCode)
            {
                dynamic content = JsonConvert.DeserializeObject(
                    response.Content.ReadAsStringAsync()
                    .Result);
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

11. Call this method from the main method and run your console application. Find out that the service hook gets created for the specified project.

12. We can now trigger any build for this project so as to execute the azure function which is given as consumer url.

Conclusion

In this article, we discussed how to create service hook for any VSTS project by using a wizard. As we used a web hook, we needed an azure function ready to be provided as HTTP url. I created this azure function of type GenericWebHook-CSharp and mode as web hook. The function in turn received JSON request which was interpreted to get required values. This article also discussed how to create a service hook programmatically.

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on Google+
Further Reading - Articles You May Like!
Author
Gouri Sohoni is a Trainer and Consultant for over two decades. She specializes in Visual Studio - Application Lifecycle Management (ALM) and Team Foundation Server (TFS). She is a Microsoft MVP in VS ALM, MCSD (VS ALM) and has conducted several corporate trainings and consulting assignments. She has also created various products that extend the capability of Team Foundation Server.


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!