Azure Serverless Functions With Real World Scenario

Azure Serverless Functions With Real World Scenario

Introduction

In this article, we will see how we can trigger Azure function and make third-party HTTP post-calls from an Azure function.

Scenario

Nowadays, IT companies are using GitHub for maintaining repositories, logging bugs etc. Many times, developers ignore the logged bug, now in order to escalate or alert every stack holder of that repository, managers prefer to create a group and post the same in that group in order to keep every stack holder on the same page. But for this, someone has to post the newly created bug into that group. Let's automate this process using an Azure function.

image.png

Steps

  1. Register at slack.com
  2. Create Slack App
  3. Configure incoming webhook in slack
  4. Verify Slack incoming webhook through Postman
  5. Create Azure function through the portal
  6. Run the function and verify in Slack
  7. Add webhook in GitHub
  8. Verify end to end flow

Register at slack.com Register in slack if it's not already been done by browsing the below URL.

slack.com/signin

Click on the link 'Create a new workspace'.

image.png

Enter your valid email address and click on the 'Next' button.

image.png

Get the confirmation code from your given mail and enter as below.

image.png

Provide your full name and click on 'Continue to Password'.

image.png

Enter the password which you can easily remember and click on 'Continue to Company Name'.

image.png

Enter company or group name and click on 'Continue to Workspace URL'.

image.png

Finally, provide the workspace name to create a workspace and click on 'Create Workspace'.

image.png

Click on 'I Agree' link.

image.png

Your workspace is created in Slack.

image.png

Create Slack App

Browse the below URL.

api.slack.com/apps?new_app=1

In the second step, we need to create a Slack App. As we are going to use to track issues, the name is given like 'CurrentIssues'.

Select the workspace and click on 'Create App' button.

image.png

Configure incoming webhook in slack Under the CurrentIssues app, we need to create Incoming Webhooks which is used to post messages from external resources into slack.

Click on 'Basic Information' from the left panel.

Click on 'Incoming Webhooks' in the right panel.

image.png

Enable 'Activate Incoming Webhooks' through the slider.

image.png

Click on 'Add New Webhook to Workspace' button.

image.png

Select the channel where you want to post the issues and click on 'Authorize' button.

image.png

Incoming Webhook URL is created, now you can copy the same by clicking on 'Copy' button.

image.png

Verify Slack incoming webhook through Postman

Open the Postman app.

Select 'POST' as Method.

Paste the previously copied URL.

Select the body tab and click on 'raw' radio button.

Add the body part in the multiline text box in JSON format, here we have added "Hello World" for testing purpose.

Click on 'Send' button.

image.png

We have posted a message from postman to slack. Let's verify the same.

Browse the below URL to check the message.

akshayblevel.slack.com/team/akshayblevel

We can see that "Hello World" message is added in the 'general' channel of slack.

image.png

Create Azure function through the portal

Let's create function app which will post the current issue details to slack and finally this function app will be triggered by git hub as soon as the bug is logged.

Before we move ahead, we need to have a basic understanding of Function App:

Azure Function is a solution where we want to execute a small piece of code or function in the cloud. We can trigger this function using an HTTP request. It provides facility to develop a serverless application in the cloud.

Click on 'Create a resource' link from the left panel.

Click on 'Compute' and 'Function App' from the right panel.

image.png

Give the proper name for the app.

Select .NET as runtime stack and keep the rest of the selection as it is.

Click on 'Create' button.

image.png

Function App is created and it shows the status as Running. Click on 'New Function' to add function,

image.png

Select the 'In-Portal' as the development environment.

image.png

Select 'Webhook + API' function which will be run whenever it receives an HTTP request.

image.png

Click on 'Create' button which will create a function with a basic template.

image.png

Add the below code snippet to the function that contains the Newtonsoft.Json reference to deserialize the request body.

Create SendSlackMessage method and call it from the main function.

SendSlackMessage generates the request body and call the slack Incoming Webhook URL to post the message to slack

#r "Newtonsoft.Json"

using System.Net;  
using Microsoft.AspNetCore.Mvc;  
using Microsoft.Extensions.Primitives;  
using Newtonsoft.Json;  
using System.Text;  

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)  
{  
    log.LogInformation("C# HTTP trigger function processed a request.");  

    string issue = req.Query["text"];  

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();  
    dynamic data = JsonConvert.DeserializeObject(requestBody);  
    issue = issue ?? data?.text;  

    if(issue != null)  
    {  
        var results = await SendSlackMessage(issue);  
        return (ActionResult)new OkObjectResult($"Issue posted to Slack successfully.");  
    }  
    else  
    {  
         return new BadRequestObjectResult("Please pass a issue on the query string or in the request body");  
    }  
}  

public static async Task<string> SendSlackMessage(string text)  
{  
    using (var client = new HttpClient())  
    {  

        Dictionary<string, string> dictionary = new Dictionary<string, string>();  
        dictionary.Add("text", text);  

        string json = JsonConvert.SerializeObject(dictionary);  

        var requestData = new StringContent(json, Encoding.UTF8, "application/json");  

            var response = await client.PostAsync(String.Format("https://hooks.slack.com/services/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"), requestData);  
            var result = await response.Content.ReadAsStringAsync();  

        return result;  
    }  
}

Run the function and verify in Slack Click on 'Run' link.

In the log, we can observe that the function is compiled and executed successfully.

image.png

We can also check the output in the Test tab.

image.png

We can see the message posted from Azure function to Slack.

image.png

Click on 'Get Function URL' under newly created function, Azure Serverless Functions With Real World Scenario

image.png

Copy the URL which will be used later on while configuring on GitHub .

image.png

Add webhook in GitHub

Log in into Github and click on any of the existing repositories.

Click on the Setting tab.

image.png

Click on 'Webhooks' from the left bar.

image.png

Click on 'Add webhook' button.

image.png

Paste the previously copied function URL in the Payload URL text box.

image.png

Check the 'Issues' checkbox.

image.png

Click on 'Add webhook' button which will create a webhook.

Find the below sample payload; i.e., Github issue information, and we can send any details from the below to Slack.

Issue Event Payload

{  
  "action": "edited",  
  "issue": {  
    "url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2",  
    "repository_url": "https://api.github.com/repos/Codertocat/Hello-World",  
    "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2/labels{/name}",  
    "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2/comments",  
    "events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2/events",  
    "html_url": "https://github.com/Codertocat/Hello-World/issues/2",  
    "id": 327883527,  
    "node_id": "MDU6SXNzdWUzMjc4ODM1Mjc=",  
    "number": 2,  
    "title": "Spelling error in the README file",  
    "user": {  
      "login": "Codertocat",  
      "id": 21031067,  
      "node_id": "MDQ6VXNlcjIxMDMxMDY3",  
      "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4",  
      "gravatar_id": "",  
      "url": "https://api.github.com/users/Codertocat",  
      "html_url": "https://github.com/Codertocat",  
      "followers_url": "https://api.github.com/users/Codertocat/followers",  
      "following_url": "https://api.github.com/users/Codertocat/following{/other_user}",  
      "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}",  
      "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}",  
      "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions",  
      "organizations_url": "https://api.github.com/users/Codertocat/orgs",  
      "repos_url": "https://api.github.com/users/Codertocat/repos",  
      "events_url": "https://api.github.com/users/Codertocat/events{/privacy}",  
      "received_events_url": "https://api.github.com/users/Codertocat/received_events",  
      "type": "User",  
      "site_admin": false  
    },  
    "labels": [  
      {  
        "id": 949737505,  
        "node_id": "MDU6TGFiZWw5NDk3Mzc1MDU=",  
        "url": "https://api.github.com/repos/Codertocat/Hello-World/labels/bug",  
        "name": "bug",  
        "color": "d73a4a",  
        "default": true  
      }  
    ],  
    "state": "open",  
    "locked": false,  
    "assignee": null,  
    "assignees": [  

    ],  
    "milestone": null,  
    "comments": 0,  
    "created_at": "2018-05-30T20:18:32Z",  
    "updated_at": "2018-05-30T20:18:32Z",  
    "closed_at": null,  
    "author_association": "OWNER",  
    "body": "It looks like you accidently spelled 'commit' with two 't's."  
  },  
  "changes": {  
  },  
  "repository": {  
    "id": 135493233,  
    "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=",  
    "name": "Hello-World",  
    "full_name": "Codertocat/Hello-World",  
    "owner": {  
      "login": "Codertocat",  
      "id": 21031067,  
      "node_id": "MDQ6VXNlcjIxMDMxMDY3",  
      "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4",  
      "gravatar_id": "",  
      "url": "https://api.github.com/users/Codertocat",  
      "html_url": "https://github.com/Codertocat",  
      "followers_url": "https://api.github.com/users/Codertocat/followers",  
      "following_url": "https://api.github.com/users/Codertocat/following{/other_user}",  
      "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}",  
      "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}",  
      "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions",  
      "organizations_url": "https://api.github.com/users/Codertocat/orgs",  
      "repos_url": "https://api.github.com/users/Codertocat/repos",  
      "events_url": "https://api.github.com/users/Codertocat/events{/privacy}",  
      "received_events_url": "https://api.github.com/users/Codertocat/received_events",  
      "type": "User",  
      "site_admin": false  
    },  
    "private": false,  
    "html_url": "https://github.com/Codertocat/Hello-World",  
    "description": null,  
    "fork": false,  
    "url": "https://api.github.com/repos/Codertocat/Hello-World",  
    "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks",  
    "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}",  
    "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}",  
    "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams",  
    "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks",  
    "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}",  
    "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events",  
    "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}",  
    "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}",  
    "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags",  
    "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}",  
    "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}",  
    "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}",  
    "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}",  
    "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}",  
    "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages",  
    "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers",  
    "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors",  
    "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers",  
    "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription",  
    "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}",  
    "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}",  
    "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}",  
    "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}",  
    "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}",  
    "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}",  
    "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges",  
    "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}",  
    "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads",  
    "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}",  
    "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}",  
    "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}",  
    "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}",  
    "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}",  
    "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}",  
    "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments",  
    "created_at": "2018-05-30T20:18:04Z",  
    "updated_at": "2018-05-30T20:18:10Z",  
    "pushed_at": "2018-05-30T20:18:30Z",  
    "git_url": "git://github.com/Codertocat/Hello-World.git",  
    "ssh_url": "git@github.com:Codertocat/Hello-World.git",  
    "clone_url": "https://github.com/Codertocat/Hello-World.git",  
    "svn_url": "https://github.com/Codertocat/Hello-World",  
    "homepage": null,  
    "size": 0,  
    "stargazers_count": 0,  
    "watchers_count": 0,  
    "language": null,  
    "has_issues": true,  
    "has_projects": true,  
    "has_downloads": true,  
    "has_wiki": true,  
    "has_pages": true,  
    "forks_count": 0,  
    "mirror_url": null,  
    "archived": false,  
    "open_issues_count": 2,  
    "license": null,  
    "forks": 0,  
    "open_issues": 2,  
    "watchers": 0,  
    "default_branch": "master"  
  },  
  "sender": {  
    "login": "Codertocat",  
    "id": 21031067,  
    "node_id": "MDQ6VXNlcjIxMDMxMDY3",  
    "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4",  
    "gravatar_id": "",  
    "url": "https://api.github.com/users/Codertocat",  
    "html_url": "https://github.com/Codertocat",  
    "followers_url": "https://api.github.com/users/Codertocat/followers",  
    "following_url": "https://api.github.com/users/Codertocat/following{/other_user}",  
    "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}",  
    "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}",  
    "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions",  
    "organizations_url": "https://api.github.com/users/Codertocat/orgs",  
    "repos_url": "https://api.github.com/users/Codertocat/repos",  
    "events_url": "https://api.github.com/users/Codertocat/events{/privacy}",  
    "received_events_url": "https://api.github.com/users/Codertocat/received_events",  
    "type": "User",  
    "site_admin": false  
  }  
}

For testing purposes, we are going to send issue title only.

Modify the function code as below, we need to get request body from git hub webhook.

image.png

Verify end to end flow

Let's verify the end to end flow, i.e., create an issue on GitHub and verify it in the Slack channel.

Click on the Issues tab inside the GitHub repository. Click on 'New issue' button and provide the required details; e.g., title.

image.png

We can see that recently created issue detail, i.e., the title, is posted under the Slack channel.

image.png

References:

  1. functions.azure.com
  2. slack.com
  3. github.com