Salesforce, Python, SQL, & other ways to put your data where you need it

Need event music? 🎸

Live and recorded jazz, pop, and meditative music for your virtual conference / Zoom wedding / yoga class / private party with quality sound and a smooth technical experience

Azure Static Web Apps minimum viable build

06 Feb 2023 🔖 devops web development integration git azure
💬 EN

Table of Contents

Below is work I did to figure out “what’s really going on” with Azure Static Web Apps and CI/CD pipelines.

The most surprising things I learned are:

  1. There’s nothing special about the interlink between your Git repository-hosting system and Azure SWA. When Azure SWA “sets up” build and deployment pipelines for you as you configure a new project, it’s just copying a .yml file into your Git-tracked codebase (your repo) and putting a deployment token into the secrets manager hosted with the copy of your Git repository stored in the cloud provider you chose.
    • Your dev folks absolutely can configure repo-to-SWA flows themselves, by hand, in their Git-hosting-plus-pipelines tool (e.g. GitHub, Azure DevOps) instead of making it part of the SWA-resource-provisioning process.
      This is kind of cool because if they were planning to run unit/integration/regression/end-to-end automated tests against built/deployed websites, they were probably going to hand-write some GitHub Action / Azure DevOps Pipeline YAML files by hand anyway, so … nifty, the “hey SWA, please build my website” code can just get all rolled up into that.
      I remember even reading that the URL built by a GitHub Action (remember, SWA offers preview live URLs for every branch or commit or something like that) is accessible as a variable in the YAML, so people who know what they’re doing in GitHub Actions should be able to pretty easily write user interface tests that simply point to “whatever URL was just built” when throwing in Selenium / Cypress / etc. tests.
    • It’s just a convenience that the GUI-based SWA-resource-provisioning process asks you about the repo you’ll be deploying from.
  2. By default (I haven’t played with locking this down), Azure SWA doesn’t really care where build instructions come from, as long as the computer emitting the instructions has a copy of the SWA resource’s deployment key.
    • Security: Guard that key!
    • Accidents: If you put the same key into 2 repos (and associated CI/CD pipeline) and then someone commits to one and then someone else commits to the other, your live site hosted by Azure SWA will display content from whichever repo+pipeline’s commit got to it last.

GitHub repo with example

See also the GitHub example “The smallest GitHub website you can make that will build an Azure Static Web App” that you can copy into your own repo and try yourself if you have an Azure environment where you can create a Static Web App, such as A Cloud Guru.


Install PowerShell and modules

Once I had PowerShell 7 installed onto my computer, I had to do a 1-time install of the Azure modules:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force

Connect PowerShell to Azure

Each time my A Cloud Guru sandbox dies and I provision another one, I have to redo this PowerShell command:

Connect-AzAccount

To double-check I’m really logged into A Cloud Guru, I run one of these 3 commands:

Get-AzContext # The best verification.
Get-AzSubscription # If I need a Tenant ID and Subscription ID, here they are.
Get-AzTenant # I forgot why I ever looked at this one but anyway it will show me some stuff too.

Create a Static Web App resource

Before I create anything, the following PowerShell command returns a null value, which is to be expected:

Get-AzStaticwebApp

Once I’m sure I’m connected to the correct Azure, I can fetch the Resource Group that A Cloud Guru put into the sandbox it spun up for me with Az Powershell and use that info to create an Azure resource called my-first-swa of type Static Web App.

$my_resource_group_name = (Get-AzResourceGroup).ResourceGroupName
$my_swa_name = 'my-first-swa'
$my_static_web_app = New-AzStaticWebApp -ResourceGroupName $my_resource_group_name -Name $my_swa_name -Location 'Central US' -SkuName 'Standard' -RepositoryUrl $null

It takes about a minute to run.

I should probably be setting RepositoryUrl and a bunch of other settings, but at the moment I’m too lazy to get all set up properly with Terraform and whatnot, and New-AzStaticWebApp seems to have an unsolved bug in it with respect to RepositoryUrl, and this is as good as I’ve gotten so far.

Validate the SWA live site has not been built

If I run this PowerShell script once it’s done, I can see the URL that my website exists at _(it’ll be something along the lines of adjective-noun-hex.integer.azurestaticapps.net):

(Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).defaultHostname

Since I didn’t set -RepositoryUrl, -Branch, OutputLocation, AppLocation, or ApiLocation as part of New-AzStaticWebApp, the following commands will all return null values at first:

(Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).repositoryUrl
(Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).branch
(Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).outputLocation
(Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).appLocation
(Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).apiLocation

And this one returns the word “None”:

(Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).provider

Building an SWA live site from code stored in GitHub

Interestingly, though, dealing with this bug made me realize that when I’m looking at https://portal.azure.com/#@azurelabs.linuxacademy.com/resource/subscriptions/my_subscription_id/resourceGroups/my_resource_group_name/providers/Microsoft.Web/staticSites/my-first-swa/staticsite, the Manage deployment token tab up at the top lets me grab a value that I can punch into a GitHub repository’s secrets – and my Static Web App couldn’t care less what repository in the whole wide world I’m accessing its API from, as long as I’m using this secret.

Once I let something like a GitHub Action blindly reach out into the internet with a repository secret stored at https://github.com/my-username/my-repo-name/settings/secrets/actions named AZURE_STATIC_WEB_APPS_API_TOKEN_YAY_I_DID_IT and a /.github/workflows/its-my-cicd.yml file along these lines (which took my single-HTML-file “app” just over a minute)

name: Azure Static Web Apps CI/CD

on:
  push:
    branches:
      - main
  pull_request:
    types: [opened, synchronize, reopened, closed]
    branches:
      - main

jobs:
  build_and_deploy_job:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true
      - name: Build And Deploy
        id: builddeploy
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_YAY_I_DID_IT }}
          repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
          action: "upload"
          ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
          # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
          app_location: "/src/web" # App source code path
          api_location: "/src/api" # Api source code path - optional
          output_location: "/output" # Built app content directory - optional
          ###### End of Repository/Build Configurations ######

  close_pull_request_job:
    if: github.event_name == 'pull_request' && github.event.action == 'closed'
    runs-on: ubuntu-latest
    name: Close Pull Request Job
    steps:
      - name: Close Pull Request
        id: closepullrequest
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_YAY_I_DID_IT }}
          action: "close"

…then:

  1. https://adjective-noun-hex.integer.azurestaticapps.net flipped from a placeholder page (“Your Azure Static Web App is live and waiting for your content” / “Your app is now live, but we don’t have your content updates. Check the deployment status in the GitHub Actions tab in your repository. Learn more about deployment from the Static Web App deployment docs.”) to a big H1 tag with “Hello World” in it.
  2. Refreshing https://portal.azure.com/#@azurelabs.linuxacademy.com/resource/subscriptions/my_subscription_id/resourceGroups/my_resource_group_name/providers/Microsoft.Web/staticSites/my-first-swa/staticsite made more things show up.
  3. The following PowerShell commands started returning non-null values:
    (Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).repositoryUrl # https://github.com/my-username/my-repo-name
    (Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).branch # main
    (Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).provider # GitHub
    

Azure SWA will respond to anything with a deployment token

So … Azure doesn’t actually really need any access to your GitHub repo, unless you want it to help you create that YAML file. And yeah, I tried this from a private GitHub repo Azure had absolutely no credentials to. GitHub needed credentials for talking to my Azure Static Web App, but if I’m not letting Azure help me write my code I store in GitHub, then Azure doesn’t need to know anything about how to authenticate into my GitHub account.

(Letting Azure write code and set secrets in my GitHub is totally how I got that YAML file from a previous attempt, FWIW. I didn’t know a thing about GitHub Actions. All I did was change 2 occurrences of the phrase “secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ADJECTIVE_NOUN_HEX” to “AZURE_STATIC_WEB_APPS_API_TOKEN_YAY_I_DID_IT”. I also deleted the old AZURE_STATIC_WEB_APPS_API_TOKEN_ADJECTIVE_NOUN_HEX secret Azure had created within https://github.com/my-username/my-repo-name/settings/secrets/actions and added my own secret.)

Note that app_location of /src/web, api_location of /src/api, and output_location of /output are marked in the YAML that Azure generated as optional … but … I mean, I’d argue not really anymore, since I didn’t bother to set them on the Azure side. I guess I haven’t tried a build, but I strongly suspect it wouldn’t haved worked without these being set on the GitHub side since I never did get around to setting them on the Azure side.

(My Git repository’s only other file, at the moment, besides the GitHub action definition, is /src/web/index.html and looks like this:)

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>A web page</title>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>

Really really

I renamed my GitHub repository by adding “-2” to the end of its name. I force-reran its Action (the one that knows how to talk to my Azure Static Web App via the SWA’s deployment secret).

GitHub Actions thinks it ran, but https://portal.azure.com/#@azurelabs.linuxacademy.com/resource/subscriptions/my_subscription_id/resourceGroups/my_resource_group_name/providers/Microsoft.Web/staticSites/my-first-swa/staticsite didn’t show any changes to where it thought the repository lived, either in the UI or in the “JSON View.”

I realized I hadn’t actually changed anything about my “app,” so I edited index.html to say “Hello Goodbye” and GitHub Actions run again. Once again, it was convinced it’d talked to SWA.

Interestingly, this time the “Source,” “Deployment history,” and “Edit workflow” links had “my-repo-name-2” in them instead of “my-repo-name” – “JSON View” had the “-2” in it as well this time – maybe it’s asynchronous and I jumped the gun. Or maybe SWA needed to actually see a change to the codebase, not just get a mysterious force-rerun from GitHub Actions.

Anyway, I could also see at https://adjective-noun-hex.integer.azurestaticapps.net that the H1 tag now says “Hello Goodbye.”

So. That’s interesting. It seems any given Azure Static Web App resource isn’t too picky which CI/CD pipeline asks it to run a build & deploy process a given codebase, as long as that pipeline knows the SWA’s deployment token.

There’s no way around using deployment tokens

As of early March 2023, there’s no way to tell a GitHub Action or Azure DevOps Pipeline that you’d like to build a codebase and deploy it into a Static Web App using, say:

  1. the Static Web App resource’s name and
  2. an alternative Azure authentication approach such as service principal credentials.

The closest anyone’s come to deploying a built codebase into Static Web Apps “using” a service principal is to add extra steps to a GitHub Action or Azure DevOps Pipeline that:

  1. use the service principal to log into the Azure CLI, and
  2. use that authentication to run az staticwebapp secrets list against the SWA’s resource name, and
  3. store the resulting deployment token into the Action/Pipeline’s runtime secrets cache (much the same way you could’ve just copied/pasted the deployment token into the repository’s longterm secrets store by hand).

Aaron Powell has one example but I haven’t quite gotten it to work. Chris Reddington got it working as well, but concluded that it seemed like more trouble and vulnerability than it’s worth, compared to just copy-pasting a deployment token into your repository’s secrets manager. I’m particularly inclined to agree with Chris after playing around and noticing that Microsoft didn’t include the Microsoft.Web/staticSites/listSecrets/action operation (necessary for az staticwebapp secrets list) in Azure’s “Website Contributor” built-in role the way they added Microsoft.Web/sites/* operations to Website Contributor, meaning you either have to create and maintain a custom Azure role for Microsoft.Web/staticSites/listSecrets/action or give the Service Principal you’re feeding your GitHub Action / Azure DevOps Pipeline an insanely broad built-in Azure role. No, thank you.

Microsoft, could you address issue 312 for enterprise customers, please?


SWA seems to have opinions about YAML file locations

The only thing that doesn’t seem to quite come out correct with the steps I’ve done so far is the “Edit workflow” link in the SWA portal webpage. It’s trying to tell me to go edit https://github.com/my-username/my-repo-name-2/tree/main/.github/workflows/azure-static-web-apps-adjective-noun-hex.yml (note the filename is not its-my-cicd.yml but instead is named after the Azure SWA’s production URL).

I’ll have to learn why and if it can be “fixed” and how. Or whether I’m supposed to just avoid using its-my-cicd.yml-type filenames and conform to Azure’s naming standards. Etc. etc. etc. But that’s a pretty good start.


Building an SWA live site from code stored in Azure DevOps Repos

The other thing I learned recently is that when you use the clicky-click web portal to set up a Static Web App, choosing Azure DevOps as your deployment puts the following file named /azure-static-web-apps-adjective-noun-hex.yml into your repo:

name: Azure Static Web Apps CI/CD

pr:
  branches:
    include:
      - main
trigger:
  branches:
    include:
      - main

jobs:
- job: build_and_deploy_job
  displayName: Build and Deploy Job
  condition: or(eq(variables['Build.Reason'], 'Manual'),or(eq(variables['Build.Reason'], 'PullRequest'),eq(variables['Build.Reason'], 'IndividualCI')))
  pool:
    vmImage: ubuntu-latest
  variables:
  - group: Azure-Static-Web-Apps-adjective2-noun2-hex2-variable-group
  steps:
  - checkout: self
    submodules: true
  - task: AzureStaticWebApp@0
    inputs:
      azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN_ADJECTIVE2_NOUN2_HEX2)
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
      app_location: "/src/web" # App source code path
      api_location: "/src/api" # Api source code path - optional
      output_location: "/output" # Built app content directory - optional
###### End of Repository/Build Configurations ######

Azure SWA really will respond to anything with a deployment token

OK so I went to https://dev.azure.com/my-ado-org-name/my-ado-project-name/_library?itemType=VariableGroups&view=VariableGroupView&variableGroupId=1&path=azure-static-web-apps-adjective2-noun2-hex2-variable-group, which I’d connected to my second SWA, and added an extra AZURE_STATIC_WEB_APPS_API_TOKEN_YAY_I_DID_IT variable with the deployment token for my first SWA.

Then I changed the .yml to azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN_YAY_I_DID_IT) and committed.

Okay, cool, we went back to “Hello World” (as was found in my ADO repo) in my-first-swa’s live website and the “JSON View” and “Source” and “Deployment history” and “Edit workflow” links auto-updated, as did:

(Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).repositoryUrl # https://dev.azure.com/my-ado-org-name/my-ado-project-name/_git/my-ado-project-name
(Get-AzStaticwebApp -ResourceGroupName ((Get-AzResourceGroup).ResourceGroupName) -Name my-first-swa).provider # DevOps

lol, proof Azure Static Web Apps will build a website off of any old CI/CD pipeline that’s gotten ahold of its deployment token. Eeep – be careful with those tokens!


Future research

Finally, here seem to be the differences between choosing to host your Git in GitHub vs. Azure DevOps Repos:

  1. GitHub lets you do branch preview websites w/o having to write the CI/CD for it yourself. I’m really excited about the possibilities of great testing maturity from this, without a lot of effort.
  2. Azure DevOps, given the right subscription model, seems to have slightly easier-to-use GUI interfaces for building out automated test suites for build/deploy. (Not that I’ve actually figured out how to get any of them working, though, so I can’t really say they’re easier.)
  3. For my research into letting the GitHub action provision-SWA-if-not-exists-yet:
    Although I don’t want to just echo this or anything (I’d want to dump it into a GitHub Action environment variable or something), it seems the following PowerShell gets me my API key. (I’d probably use the az CLI commands, though, when inside a GitHub Action, not PowerShell syntax):
     ((Get-AzStaticWebAppSecret -ResourceGroupName $my_resource_group_name -Name $my_swa_name).ToJsonString() | ConvertFrom-Json).properties.apiKey
    
    • The only problem is … how does the repo connect to the az CLI with authority to create SWA resources, but only of a certain name? Like, can you even provision permissions that have to do with things that don’t exist yet?
--- ---