Enabling Azure storage static website hosting using Pulumi and C#


TL;DR

Enabling static website hosting in Azure storage using the Azure Resource Manager (ARM) isn’t possible as the setting isn’t accessible via ARM. Tools such as ARM templates, Terraform or Pulumi cannot enable this feature natively and therefore require workarounds (such as invoking shell commands). This post describes how to enable the static website hosting feature when using Pulumi with C# by utilising the Azure storage .NET SDK in conjunction with Pulumi. You can find the sample project on my GitHub page

Introduction

I’ve previously written about hosting static websites in Azure and I find this is a great way of reducing the number of Azure app services you have to run when hosting content such as blogs, or even single page applications built using Angular, React or any number of other front-end technologies. The other big benefit is cost, static sites cost a fraction of the cost of a web server.

Pulumi is a fantastic tool for provisioning infrastructure and managing it, in some ways its similar to using ARM templates or Terraform but with a key difference. With Pulumi you can use actual programming languages to provision the infrastructure (Typescript, .NET, Go), for me as a .NET developer this means I can use C# to manage my infrastructure rather than having to deal with ARM or Terraform (it’s worth the C# support is in preview at the time of writing this post.

Enabling Static Hosting

Provisioning a storage account using Pulumi is pretty straightforward, you can see an example in the docs. I won’t go into the details here as this post is focussed on enabling the static hosting.

If you’re using TypeScript there is an example that shows how to enable the static hosting feature of Azure storage, that approach is using a dynamic provider to wrap calls to the Azure CLI to enable the static hosting. I’m using C# and unfortunately at the time of writing there isn’t support for dynamic providers for C#.

After some head scratching I felt it should be possible to use the Azure storage .NET SDKs to enable the static website hosting feature without needing to resort to invoking the Azure CLI from a spawned process, after all I’m writing C# anyway so bringing in the SDKs shouldn’t be a big deal. The approach Pulumi takes for passing around the names of resources may be a little tricky to get your head around initially, I raised a GitHub issue and Mikhail from the Pulumi team promptly gave me enough information to allow me to solve the problem.

Let’s jump into the specific solution, which you can also find on my GitHub page

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.Storage.Shared.Protocol;
using Pulumi;
using Pulumi.Azure.Core;
using Pulumi.Azure.Storage;

class Program
{
    static Task<int> Main()
    {
        return Deployment.RunAsync(() => {

            // Create an Azure Resource Group
            var resourceGroup = new ResourceGroup("mystaticsite");

            // Create an Azure Storage Account
            var storageAccount = new Account("mysite", new AccountArgs
            {
                ResourceGroupName = resourceGroup.Name,
                EnableHttpsTrafficOnly = true,
                AccountReplicationType = "LRS",
                AccountTier = "Standard",
                AccountKind = "StorageV2",
                AccessTier = "Hot",
            });
                // We can't enable static sites using Pulumi (it's not exposed in the ARM API).
                // Therefore we have to invoke the Azure SDK from within the Pulumi code to enable the static sites 
                // The code in the Apply method must be idempotent.
                storageAccount.PrimaryBlobConnectionString.Apply(async v => await EnableStaticSites(v) );
            
            // Export the Web address string for the storage account
            return new Dictionary<string, object>
            {
                { "Site-Url", storageAccount.PrimaryWebEndpoint },
            };
        });


        static async Task EnableStaticSites(string connectionString)
        {
            CloudStorageAccount sa = CloudStorageAccount.Parse(connectionString);
           
            var blobClient = sa.CreateCloudBlobClient();
            ServiceProperties blobServiceProperties = new ServiceProperties();
            blobServiceProperties.StaticWebsite = new StaticWebsiteProperties
            {
                Enabled = true,
                IndexDocument = "index.html",
               // ErrorDocument404Path = "404.html"
            };
            await blobClient.SetServicePropertiesAsync(blobServiceProperties);

        }
    }
}

The key part of this is the line storageAccount.PrimaryBlobConnectionString.Apply(async v => await EnableStaticSites(v) ); which is passing the primary connection string of the storage account into my method which then uses this and the Azure storage SDK to enable the static storage feature. If you’re confused by the Apply , you can think of it as a mechanism for retrieving the runtime values of resources that are provisioned, take a look at the Pulumi docs, which explains this better than I can.

Once we have the feature enabled we can retrieve the primary web endpoint which will give us the URL of the static site.

It’s important to note that the method we invoke must be idempotent, as it will be invoked multiple times in the lifecycle.

We can now invoke Pulumi up and providing we’ve setup our Azure credentials properly it should create a resource group and storage account with static hosting enabled. We can now go ahead and deploy our static content to this storage account.

Summary

Pulumi is a fantastic tool for managing your infrastructure using a real programming language, this post showed how you can invoke the Azure storage SDK to enable features not otherwise accessible to Pulumi (or the Azure Resource Manager API).

Back to home