Introduction

As part of the .NET Advent Calendar I thought I’d try something new and after a suggestion on Twitter I decided that I would investigate basic uses of Pulumi to deploy to Azure. As you may have seen from my previous posts I am interested in Azure Static Web Apps using Azure Functions as a backend API so I thought this would be a perfect opportunity to look at deploying this setup using Pulumi.

Disclaimer

I am a complete n00b at infrastructre as code. The below is purely through trial and error and your mileage may vary. So please be nice :-)

Code Setup

To start off we are going to need a repository to deploy. For this example I am going to use my ClaimsPrincipalAccessor example from an earlier blog post. This has some extra code in but is still a relative basic Azure Static Web Application with an API. To use this you will need to fork the repository.

Pulumi Setup

A lot of the setup was based off the awesome documentation from Pulumi link here so please follow the tutorial on this site to get your pulumi cli setup and Azure account linked etc. I would be doing diservice to their documentation to try and repeat it here. Don’t worry, I’ll wait!

All set?

Creating a new project

Once all the connectivity is setup we need to create a new Pulumi deployment project. This allows us to construct our deployment infrastructure in a programming language which we are familiar with. As I am a .NET developer I chose C# for this.

On the command line we need to navigate to your folder of choice in a terminal window. Once the folder structure is setup run the following command to create the project.

pulumi new azure-csharp

You will be presented with various questions to answer. I would recommend with sticking with the defaults it suggested for now. However they relate to the name of your project, description, your organisation, Azure region location (see below) and the “stack” you are creating so it will be worth investigating more into these options at a later time.

Below is the example output in the terminal once creating a new project.

C:\Temp\pulumi-test> pulumi new azure-csharp
This command will walk you through creating a new Pulumi project.

Enter a value or leave blank to accept the (default), and press <ENTER>.
Press ^C at any time to quit.

project name: test-project
project description: (A minimal Azure Native C# Pulumi program) This is a test project for the blog post
Created project 'test-project'

Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
stack name: (dev)
Created stack 'dev'

azure-native:location: The Azure location to use: (WestUS) westeurope
Saved config

Installing dependencies...

running 'dotnet build -nologo .'
  Determining projects to restore...

  Restored C:\Temp\pulumi-test\test-project.csproj (in 33.43 sec).

  test-project -> C:\Temp\pulumi-test\bin\Debug\netcoreapp3.1\test-project.dll



Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:35.12

'dotnet build -nologo .' completed successfully
[resource plugin azure-native-1.49.0] installing
Downloading plugin: 24.48 MiB / 24.48 MiB [=========================] 100.00% 4s
Finished installing dependencies

Your new project is ready to go!

To perform an initial deployment, run 'pulumi up'

warning: A new version of Pulumi is available. To upgrade from version '3.16.0' to '3.19.0', visit https://pulumi.com/docs/reference/install/ for manual instructions and release notes.
C:\Temp\pulumi-test>

In the create project documentation it walks through how to do this for more information.

Due to the resource we are going to be using there maybe a restriction on which region it can be deployed to. There was a limitation during the early phases however this may have been removed now. Although I can’t find the documentation to confirm this. The target region also is around where the Azure Functions API is hosted and not the client of the web app. The Static Web App service is a globally distributed service so the client app is “everywhere”. If you are concerned about where code is deployed and data etc. I would recommend doing some further research as this may change (or I may have got it wrong!).

The regional setting can be changed in the specific environment/stack config file eg Pulumi.dev.yaml after the setup has run.

config:
  azure-native:location: westeurope

What is a “Stack”?

A stack is essentially a description of an environment from what I can make out. It’s not “the” environment itself as you could theoretically deploy the same “stack” to many different environments. However it can be more seen as an environment template. The default option the new command offers is “dev” to imply the development environment. You can however create a number of stacks but this is out of the scope of this post.

Adding reference to Azure Native

The next stage is to setup your azure static web app. I don’t know if this is because it’s a new concept or not but we now have to add a nuget package reference to the release project csproj file. This is exactly the same as adding a nuget package to your application so nothing to worry about.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Pulumi.AzureNative" Version="1.44.0" />
  </ItemGroup>

</Project>

This will allow access to not only the static web application functionality but also to other resource setups. More information can be found here.

Github access token

We will need to get an access token to allow for the process to get access to the source code. This should be provided as a variable in your production deployment stack and kept as a secret somewhere. However as this is a demo, and I am still learning, I have decided to put the token directly into the source code for now.

DO NOT DO THIS IN PRODUCTION CODE!

To generate your GitHub access token you navigate here.

Setting Up the Deployment

By this point we have all the required data we need to get a deployment going. However we have not actually created anything to run the deployment.

On setting up a new Pulumi deployment project it will have generated some examples in the “MyStack.cs” file to deploy a new storage account. Delete out the example content so we are left with:

class MyStack : Stack
{
    public MyStack()
    {

    }
}

First off we need to create a new resource group. Your resources in Azure have to be grouped by a resource group. This allows for logical grouping as well as improved management. It’s also very handy for deleting all related resources if you are testing out something.

var resourceGroup = new ResourceGroup("rg-my-test-website");

We have now a resource group which has the name “rg-my-test-website”. Through out the rest of the code you will use the resourceGroup variable when relating the resources to the group.

Next up we want to create the Azure Static Web App setup. In the Pulumi world this is called a StaticSite.

var staticSite = new Pulumi.AzureNative.Web.StaticSite("staticSite", new Pulumi.AzureNative.Web.StaticSiteArgs
{
    // ** snip **
}

Along with the instantiation we now need to tell it some information to allow for the process to run. If you are familiar with the GitHub action or the AzureDevOps yaml step which requires this information as well then the items will map 1 to 1.

var staticSite = new Pulumi.AzureNative.Web.StaticSite("staticSite", new Pulumi.AzureNative.Web.StaticSiteArgs
{
    Branch = "main",
    BuildProperties = new Pulumi.AzureNative.Web.Inputs.StaticSiteBuildPropertiesArgs
    {
        ApiLocation = "./src/Api",
        AppArtifactLocation = "wwwroot",
        AppLocation = "./src/Client",
    },
    Location = "westeurope",
    Name = "testStaticSite",
    RepositoryToken = "ghp_sc**snip**",
    RepositoryUrl = "https://github.com/WestDiscGolf/ClaimsPrincipalAccessor",
    ResourceGroupName = resourceGroup.Name,
    Sku = new Pulumi.AzureNative.Web.Inputs.SkuDescriptionArgs
    {
        Name = "Free",
        Tier = "Free",
    },
});

So as we can see from the above we get to name the site “staticSite” and this will become the resource name in Azure.

We then need to define the Build properties of the static web application. These are the same values the GitHub/AzureDevOps actions require and are related to the type of application you are building and if they have an associated Azure Functions API. In our example we are wanting to build a Blazor WASM client, so we specify the source location of ./src/Client and the build output wwwroot. These values will be different depending on the type of front end framework you are building and your build process. We are also wanting an Azure Functions api backend to be built and deployed. This is defined in ApiLocation to point to the source location. All these paths are based off the root of the git repository.

Next up we have the location. In this example I have hardcoded it to “westeurope” but next steps would be to introduce variables for these values.

Then we have the name of the site. This will be the resource name as presented in the Azure Portal. The important item to note about Azure Static Web applications is you don’t have control over the fully qualified domain name however adding a custom domain is free. So it is encouraging production sites to be put behind a custom domain. This is out of the scope of this post however is relatively straight forward to do in the Azure Portal.

The RepositoryToken and RepositoryUrl are the values to access the source on GitHub. The Branch is also related here to know which branch of code to look at. All the examples are based on the source being in GitHub but the documentation says “URL for the repository of the static site.” so it maybe possible to access the code from any git repository.

We’ve got the resource name being specified from the resourceGroup variable we created earlier. And then finally the SKU on which we want to create it. The example code has these values as “Basic” however these don’t map to the Azure Static Web application SKUs as far as I can make out. Creating various resources you will need to determine the correct SKU values for your use case.

Note: The name of the resources appear with a random charactor string value as a suffix in the Azure Portal.

Running the deployment

We’ve got the stack setup. We have the resources we want configured. So now it is time to actually “start up” and provision the stack in Azure.

To do this we execute the following command in the root of the deployment project we are creating.

pulumi up

This will launch the build and deploy dialog. It will run through what resources are available, if any, what needs to be created etc. and then present back to the user what it believes it needs to do. This allows for incremental changes and deployments. In our example we are creating a new set of resources from scratch so all the options in the “Plan” are to create them.

C:\Temp\pulumi-test> pulumi up
Previewing update (dev)

View Live: https://app.pulumi.com/WestDiscGolf/test-project/dev/previews/c47bf49c-df39-4bfb-b431-049c137453ef

     Type                                     Name              Plan
 +   pulumi:pulumi:Stack                      test-project-dev  create
 +   ├─ azure-native:resources:ResourceGroup  resourceGroup     create
 +   └─ azure-native:web:StaticSite           staticSite        create

Resources:
    + 3 to create

Do you want to perform this update?  [Use arrows to move, enter to select, type to filter]
  yes
> no
  details

Once “yes” is selected. This will access the GitHub repo and start the GitHub action to build and deploy the Azure Static Web application. If there isn’t already a GitHub action to build and deploy your Azure Static Web App it will create it for you. Due to this your GitHub access token has to have enough permissions to interact with the repo.

More details can be found on Deploy the Stack.

Accessing the Site

Once Pulumi terminal completes and the resources are provisioned. You will be able to see that the GitHub action has been created and executed. At this point we will need to wait until that has completed and the site resources have been deployed. The generated url can be found either through the Azure Portal or by looking at the GitHub action as it will have the name referenced.

My example of this is : https://gray-beach-0a03f5803.azurestaticapps.net/

Your name will be different but follow the same pattern. And that’s it we have provisioned and deployed an application from GitHub into Azure.

Tidying Up

As all good citizens of demoware it is time to tidy up. This can either be done through the Azure Portal to delete the resource group which we have created however this will leave Pulumi in a limbo state. This is not ideal.

To clear down the release details and Azure resources we need to execute the “destroy” command. Now be careful this will destroy everything for the specified project so make sure you do it on the right one!

pulumi destroy

Once you’ve confirmed to delete it all you just need to wait for it to tidy itself up.

Conclusion

In this post we have touched ever so briefly on how to work with Infrastructure as Code using Pulumi. We have looked a very basic example of getting Pulumi to provision and deploy, via GitHub actions, an Azure Static Web Application with an Azure Functions backend.

This is all new to me. Deployments haven’t been my main, or even a part, of my day to day role for a number of years now. I can see how this would have been really powerful a number of years ago when I was deploying regularly into Azure. The ability to provision and setup the entire environment in a code language which I am familiar is awesome. The ability for it to tell what has changed and what changes need to be applied is very powerful.

I hope you’ve enjoyed the post and the rest of .NET Advent Calendar. If you have any thoughts or comments or just want to say “Hi” I can be found on Twitter @WestDiscGolf.