Blogging with Azure Static Web apps - Part 3

Jan 21, 2022 12:11 PM

Personal Blog
Microsoft
Azure
Bicep
YAML
Azure DevOps
Azure Pipelines
Azure Static Web Apps

Previously

In my previous blog from last week I talked about how to get the Azure DevOps GIT to work as backend by being able to also login with your AAD account and push your blog to the GIT repo to start the Azure pipeline again and to publish your newly written blog.

Now let's continue the journey and see if we can apply a bit more automation to the Azure pipeline and add a .Bicep so we can release it where we want in our environment.

Azure Service Connection

Since we are about to automate a bit more, we need to have a Service Principal in order to execute specific Azure commands. For this we need an Azure Service Connection within our Azure DevOps environment.

To make such a connection, go to your Project tab and click on Project Settings in the left bottom corner.

Underneath the Pipelines category, you will see the Service connections Tab. Click on it. In the right top corner, click on New service connection, choose the Azure Resource Manager option and continue by clicking on Next.

Choose the Service principal (Automatic) and continue.

Your subscriptions will be loaded, wait for that and choose the subscription on which your Azure Static Web App currently runs.

Filling out the resource group is not required, leave this blank if you want the connection to count for the whole subscription.

Give the connection a proper name and check the box at Grant access permission to all pipelines if you want all your Azure Pipelines to be able to use this connection. Don't forget to Save your connection.

Azure Static Web App Bicep

Now that the connection has been set, it is possible to talk with Azure and execute commands, allowing us to also deploy bicep files via the Azure CLI.

For this example, the bicep doesn't need to be very fancy and the options for an Azure Static Web App are also not that much, making it quite easy at that. If you do want to see all the options, you can check them here.

param location string = resourceGroup().location

resource StaticWebApp 'Microsoft.Web/staticSites@2021-02-01' = {
  name: '{Your Static Web App Name}'
  location: location
  tags: {
  }
  sku: {
    name: 'Free'
    tier: 'Free'
  }
  properties: {
    allowConfigFileUpdates: true
    stagingEnvironmentPolicy: 'Enabled'
  }
}

Add this code to your Azure DevOps Repo under the name main.bicep.

Azure Pipelines

When doing a deployment, I don't like to wait for all kinds of steps to be done if they are not needed. Like in this example, if a .bicep has been deployed and the service is present within Azure there is no need to run the step for deploying the infrastructure over and over again when you post a blog, right?

Luckily we can make this conditional! Let's look at it bit by bit.

Validation of resources

In the code below, I start with checking whether the resource group exists. If this doesn't exist, I also know the Static Web App will not exist, just as easy as that.

If it does exist, I want to check whether the Static Web App exists and depending on that result I want to fill a variable which can be used for conditional checking the upcoming stages.

- stage: Validate
  jobs: 
  - job: Validate
    steps:
    - task: AzureCLI@2
      displayName: 'Validate resources'
      name: validated
      inputs:
        azureSubscription: $(azureServiceConnection)
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |
          ResourceGroup=$(az group exists --name $(resourceGroupName))
          [ -n "$(az resource list --resource-group $(resourceGroupName) --resource-type 'Microsoft.Web/staticSites' --query [].name --output tsv)" ] && Resource=true || Resource=false
          VALIDATE=false
          [[ $ResourceGroup == "true" && $Resource == "true" ]] && VALIDATE=true
          echo "##vso[task.setvariable variable=validate;isOutput=true]$VALIDATE"

Deploying the Bicep

With the results from the Validate stage, I now know whether it is going to be needed to deploy the infrastructure or not. In case it doesn't, it will rollout the Bicep file.

- stage: Deploy_Bicep
  dependsOn: Validate
  condition: 
      and 
      (
        succeeded(), 
        eq(dependencies.Validate.outputs['Validate.validated.validate'], 'false')
      )
  jobs:
  - job: DeployInfrastructure
    steps:
    - task: AzureCLI@2
      displayName: 'Deploy infrastructure'
      inputs:
        azureSubscription: $(azureServiceConnection)
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |
          az group create --name $(resourceGroupName) --location $(location)
          az deployment group create --resource-group $(resourceGroupName) --template-file $(templateFile)

Build and Deploy the Web Code

This part is a bit more complex. First, I check if the Validate has been successful and whether the deployment of the bicep was successful or whether it skipped. After this, I get the DeploymentToken (APIKey) from the existing service/deployed service and use this in the deployment of the Static Web App itself, thereby skipping the need of manually adding the key to a variable.

- stage: Build_And_Deploy_Code
  dependsOn: 
      - Validate
      - Deploy_Bicep
  condition: |
      and
      (
        eq(dependencies.Validate.result, 'succeeded'),
        in(dependencies.Deploy_Bicep.result, 'Succeeded', 'Skipped')
      )
  jobs:
  - job:
    steps:
    - task: AzureCLI@2
      displayName: 'Get ApiKey'
      inputs:
        azureSubscription: $(azureServiceConnection)
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |
          resourceName=$(az resource list --resource-group $(resourceGroupName) --resource-type 'Microsoft.Web/staticSites' --query [].name --output tsv)
          APIKEY=$(az staticwebapp secrets list --name $resourceName | jq -r '.properties.apiKey')
          echo "##vso[task.setvariable variable=apiKey;issecret=true]$APIKEY"

    - task: AzureStaticWebApp@0
      displayName: 'Build & Deploy Code'
      inputs:
        app_location: '/'
        api_location: ''
        output_location: 'build'
        azure_static_web_apps_api_token: $(apiKey

Full YAML

All the parts combined the YAML will look like this:

trigger:
- master

name: Deploy static web app

variables:
  azureServiceConnection: 'AzureConnection'
  resourceGroupName: '{Your ResourceGroupName}'
  location: 'westeurope'
  templateFile: 'main.bicep'

pool:
  vmImage: ubuntu-latest

stages:
- stage: Validate
  jobs: 
  - job: Validate
    steps:
    - task: AzureCLI@2
      displayName: 'Validate resources'
      name: validated
      inputs:
        azureSubscription: $(azureServiceConnection)
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |
          ResourceGroup=$(az group exists --name $(resourceGroupName))
          [ -n "$(az resource list --resource-group $(resourceGroupName) --resource-type 'Microsoft.Web/staticSites' --query [].name --output tsv)" ] && Resource=true || Resource=false
          VALIDATE=false
          [[ $ResourceGroup == "true" && $Resource == "true" ]] && VALIDATE=true
          echo "##vso[task.setvariable variable=validate;isOutput=true]$VALIDATE"

- stage: Deploy_Bicep
  dependsOn: Validate
  condition: 
      and 
      (
        succeeded(), 
        eq(dependencies.Validate.outputs['Validate.validated.validate'], 'false')
      )
  jobs:
  - job: DeployInfrastructure
    steps:
    - task: AzureCLI@2
      displayName: 'Deploy infrastructure'
      inputs:
        azureSubscription: $(azureServiceConnection)
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |
          az group create --name $(resourceGroupName) --location $(location)
          az deployment group create --resource-group $(resourceGroupName) --template-file $(templateFile)

- stage: Build_And_Deploy_Code
  dependsOn: 
      - Validate
      - Deploy_Bicep
  condition: |
      and
      (
        eq(dependencies.Validate.result, 'succeeded'),
        in(dependencies.Deploy_Bicep.result, 'Succeeded', 'Skipped')
      )
  jobs:
  - job: DeployCode
    steps:
    - task: AzureCLI@2
      displayName: 'Get ApiKey'
      inputs:
        azureSubscription: $(azureServiceConnection)
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |
          resourceName=$(az resource list --resource-group $(resourceGroupName) --resource-type 'Microsoft.Web/staticSites' --query [].name --output tsv)
          APIKEY=$(az staticwebapp secrets list --name $resourceName | jq -r '.properties.apiKey')
          echo "##vso[task.setvariable variable=apiKey;issecret=true]$APIKEY"

    - task: AzureStaticWebApp@0
      displayName: 'Build & Deploy Code'
      inputs:
        app_location: '/'
        api_location: ''
        output_location: 'build'
        azure_static_web_apps_api_token: $(apiKey)

When you apply the new YAML code, it will automatically trigger the CI/CD and everything will run. If you do want to make sure this doesn't happen in the future, you can add this bit of code to the trigger:

trigger:
  branches:
    include:
    - master
  paths:
    include:
    - '*'
    exclude:
    - '**/*.yml'
    - '**/*.yaml'
    - '**/*.bicep'

When your pipeline has finished running, the results of the pipeline should be similar to what they would be if you already had your Azure Static Web App running.

Screenshot 2022-02-11 at 12.29.31.png

What's next?

Together with my colleague Soufian, we have been working in getting a Discord Bot up and running in Azure. You can read all about it in next week's blog.