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.
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.