Fun With Azure Functions and GitHub Webhooks

Part of my blogging process since I started was to create card images for each post that I could use for my meta tags. For all the benefits it brings like personalisation when I cross-post my articles on Dev.to I should mention it was not fun having to create these images using Gimp every time I wrote up a blog post. To solve this, I turned to my motto to Automate everything.

I wanted to automate the process of generating these images whenever I added a new blog post. Seeing as I host all my images on Azure Blob Storage, I figured having the images generated via an Azure Function was going to be an obvious design decision. Secondly, I wanted to trigger this process automatically as soon as I pushed the new blog post to my repository on GitHub, for this a webhook to call my Http trigger Azure Function would be enough.

Azure Function

For the Azure Function side of this process, what I wanted to do was generate an image that I could use as the card when posting my article link to twitter, like the image above. For this, I already had my placeholder image without the description of said article.

All that was left for me to do was to load the placeholder image and get the description of the blog post article from my repository. Below is how did this:

private static async Task<PostMetaTagImage> GetBlogPostCardDescription(ILogger log)
{
PostMetaTagImage cardDescription = new PostMetaTagImage();
try
{
string content = await MakeGitHubAPICall("https://api.github.com/repos/<website-repo>/commits");
List<CommitsData> commits = JsonConvert.DeserializeObject<List<CommitsData>>(content);
CommitsData latestCommit = commits.OrderByDescending(x => x.commit.author.date).FirstOrDefault();
//Get Files in latestCommit
content = await MakeGitHubAPICall($"https://api.github.com/repos/<website-repo>/commits/{latestCommit.sha}");
CommitResponse commitDetail = JsonConvert.DeserializeObject<CommitResponse>(content);
//Iterate files to find aded file
commitDetail.files.ForEach(f =>
{
//If this file is in the contents/static/api/post/ directory and has been added, its a new blog post,
//so we have to generate a new card image
if(f.status == "added" && f.contents_url.Contains("contents/static/api/post/"))
{
//Load the blog post json file that was added
content = MakeGitHubAPICall(f.contents_url).Result;
ContentResponse postDetails = JsonConvert.DeserializeObject<ContentResponse>(content);
//Get post details from Base64 encoded string
byte[] data = Convert.FromBase64String(postDetails.content);
string decodedString = Encoding.UTF8.GetString(data);
//Deserialize string from Base64
BlogPost p = JsonConvert.DeserializeObject<BlogPost>(decodedString);
//Set the card description
cardDescription.PostTitle = p.results[0].id;
cardDescription.PostMetaTagImageDescription = p.results[0].meta.description;
}
});
}
catch(Exception ex)
{
log.LogError($"An error occured getting post description for Card from GitHub API: " + ex.Message);
}
return cardDescription;
}

In the code above we are making use of the GitHub API. I use the API to get the latest commit for the repository and iterate through the new files that were added to it. I then look to see if any new file was added to the contents/static/api/post/ directory. This is the directory with all my articles. My website is a static file application, so what I do next is to load the json file with the blog post details and extract the post id and description I will use on the image.

Now that I have the description to use on the image, I can go ahead and generate the image.

private static MemoryStream GenerateBlogPostMetaCardWithDescription(string description)
{
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
Bitmap newBitmap;
using (var bitmap = (Bitmap)LoadImageTemplate())//load the image file
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
using (Font arialFont = new Font("Arial", 22))
{
Rectangle rect1 = new Rectangle(20, 220, 550, 40);
graphics.DrawString(description, arialFont, Brushes.White, rect1, format);
}
}
newBitmap = new Bitmap(bitmap);
}
MemoryStream memoryStream = new MemoryStream();
newBitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Jpeg);
memoryStream.Position = 0;
return memoryStream;
}

The code above loads the placeholder image which is just the image in the example above without the text and the curly brackets, then I get the start position on the image and use the DrawString method in System.Drawing to plot the text on the image.

//Generate image
var memoryStream = GenerateBlogPostMetaCardWithDescription("{ " + metaCardDescription.PostMetaTagImageDescription + " }");
//Create the image in Blob Storage
CloudBlockBlob blob = blobContainer.GetBlockBlobReference($"{metaCardDescription.PostTitle}.png");
blob.Properties.ContentType = "image/png";
blob.UploadFromStream(memoryStream);
log.LogInformation($"Post image card generation for post {metaCardDescription.PostTitle} was successful. Image was uploaded to Blob Storage");

Once I have the image generated, I then upload it to Blob storage and save it with its name set to the post id we got earlier from the GitHub API. This way I can ensure that setting the image url for the meta tag on the blog post to https://storage-account-url/website/post-id.png will render the image for this post.

That concludes the Azure Function, but we still need to invoke the function. For that, we’re going to use a GitHub webhook.

GitHub Webhook

Webhooks are automated messages sent from apps when something happens. In the case of GitHub, that would be when pushing new code, when a repo is forked and so on. For my scenario, we want to make use of the Push event. This would ensure every time I push to my repo, my Azure Function would be called and if a new blog post was added a new image would be created for it.

To create a webhook takes less than a minute. Navigate to the setting of your GitHub repo, select the Webhooks menu item. On the webhooks page click on the Add button and complete the following fields:

Payload URL: This is the where to send the payload of the GitHub event. In my case, it would be the payload of the Push event and the URL to my Azure Function.

This payload is of no use to me because I will be using the GitHub API to get everything I need. I use the GitHub API because this payload does not include the inner details of the blog post that was added.

Content type: The content type of the payload. I selected Json.

The last thing you need to do is select the event you want this Webhook to be triggered on. In my case, it was the Push event.

Summary

There you have it. I have automated the process of generating the images for each article I use for the meta tags on my posts. Now all I do is write my posts and push to GitHub. I host my website on Netlify and it too uses a Webhook to publish my website after I push to GitHub, so as soon my article is published to my website the image has already been created and save to my storage account on Azure.

Share: