Pulumi IaaC => IaaC# (Infrastructure as CSharp) - Deploying Azure bots with Pulumi

I’ll admit that, until a week ago, I had never heard of Pulumi, but when they recently announced support for .NET - they got my attention. I’ve worked alot with JSON based Azure Resource Manager (ARM) templates in the past - but these quickly become complicated to author when you have many different Azure resources.

Whilst Terraform offers it’s own HashiCorp Configuration Language (HCL), that or working with YAML makes a bit of me want to die - the syntax seems we’ve gone back an era in computing. So, after Pulumi’s C# announcement I wanted to take a further look and see what the noise was all about. What better way to kick the tyres than to deploy a brand new Azure Bot Service instance directly into Azure.

You can read about the Pulumi architecture and high level concepts, to understand some of the basics and terminology. Under the covers Pulumi uses the Azure GO SDK which calls the Azure Management API. Once you’ve configured your IaaC, you can then integrate it into Azure DevOps Pipelines or GitHub Actions.

Deploying Azure Bot Service with Pulumi C# SDK

Essentially to deploy a Web App Bot within Azure Bot Services (ABS) requires several things:

Azure resource Description Optional
Resource Group Container for the Azure resources
AAD Application ID and secret AAD App registration and Authentication for the bot
App Service Plan Defined set of compute resources for web app
AppService Web API of the bot application
Bot Service ABS bot registration
Application Insights Telemetry/logging for bot application *
Cognitive Services Universal key for LUIS/QnA Maker etc *

Declaring this in Pulumi CSharp

Let’s take a look at the individual code segments that can be used to provision the above Azure services. A link to a complete sample is provided at the bottom of this post:

Azure Resource Group

var resourceGroup = new ResourceGroup("botservice-rg");

Azure AD Application Registration

This requires the additional Pulumi.Azuread provider nuget package (this took me a while to figure out).

var msa = new Application("msapp", new ApplicationArgs
{
    Oauth2AllowImplicitFlow = false,
    AvailableToOtherTenants = true,
    PublicClient = true
});

Azure AD Application Secret

Here we can use the additional Pulumi.Random provider nuget package to generate a strong password for the secret value and use that for the application password.

var pwd = new Random.RandomPassword("password", new Random.RandomPasswordArgs
{
    Length = 16,
    MinNumeric = 1,
    MinSpecial = 1,
    MinUpper = 1,
    MinLower = 1
});

var msaSecret = new ApplicationPassword("msasecret", new ApplicationPasswordArgs
{
    ApplicationObjectId = msa.ObjectId,
    EndDateRelative = "8640h", // set whatever expiry you want
    Value = pwd.Result
});

Azure App Service Plan

var appServicePlan = new Plan("asp", new PlanArgs
{
    ResourceGroupName = resourceGroup.Name,
    Kind = "App",
    Sku = new PlanSkuArgs
    {
        Tier = "Basic",
        Size = "B1"
    },
});

Azure Application Insights

var appInsights = new AppInsights.Insights("ai", new AppInsights.InsightsArgs
{
    ApplicationType = "web",
    ResourceGroupName = resourceGroup.Name
});

Azure Application Insights API Key

var appInsightApiKey = new AppInsights.ApiKey("ai", new AppInsights.ApiKeyArgs
{
    ApplicationInsightsId = appInsights.Id,
    ReadPermissions = "api",
});

Azure Cognitive Service Key

Universal key for any Azure Cognitive Services used by your bot.

var luis = new Cognitive.Account("cs", new Cognitive.AccountArgs
{
    Kind = "CognitiveServices", // includes LUIS
    ResourceGroupName = resourceGroup.Name,
    Sku = new Cognitive.Inputs.AccountSkuArgs() { Name = "S0", Tier = "Standard" }
});

Azure App Service

The web (API) application uses the Azure AD details and any Cognitive Service keys declared earlier.

var app = new AppService("app", new AppServiceArgs
{
    ResourceGroupName = resourceGroup.Name,
    AppServicePlanId = appServicePlan.Id,
    AppSettings =
    {
        { "WEBSITE_RUN_FROM_PACKAGE", codeBlobUrl },
        { "MicrosoftAppId", msa.ApplicationId },
        { "MicrosoftAppPassword", msaSecret.Value },
        { "LuisApiKey", luis.PrimaryAccessKey },
    },
    HttpsOnly = true
});

Azure Bot Service

Then finally the Azure Bot Service pieces it all together.

var bot = new Bot.WebApp(botName, new Bot.WebAppArgs
{
    DisplayName = botName,
    MicrosoftAppId = msa.ApplicationId,
    ResourceGroupName = resourceGroup.Name,
    Sku = "F0",
    Location = "global",
    Endpoint = Output.Format($"https://{app.DefaultSiteHostname}/api/messages"),
    DeveloperAppInsightsApiKey = appInsightApiKey.Key,
    DeveloperAppInsightsApplicationId = appInsights.AppId,
    DeveloperAppInsightsKey = appInsights.InstrumentationKey
});

Then we can output the results

Take the below outputs and put them straight into the Bot Framework Emulator and you’ll be able to test the newly deployed bot. Alternatively, use the Azure Portal Azure Bot Service - Test in Webchat feature.

return new Dictionary<string, object>
{
    { "Bot Endpoint", bot.Endpoint },
    { "MicrosoftAppId", msa.ApplicationId },
    { "MicrosoftAppPassword", msaSecret.Value }
};

Execute the Pulumi CLI

$ pulumi stack init dev
$ az login
$ pulumi config set azure:location "North Europe"
$ pulumi config set botName PulumiBot1
$ pulumi up

Full source code sample

At the time of writing I’ve submitted a PR for a full sample here: https://github.com/pulumi/examples/pull/464

Full sample published here: https://github.com/pulumi/examples/tree/master/azure-cs-botservice