Working with Bicep means you must deploy your infrastructure with a pipeline. There are a few steps to create your pipeline using GitHub actions.
- Having an identity to interact with your Azure tenant
- Testing your code before trying to deploy anything, you don’t have to leave an error before going further
- Optional, you may want to verify what will happen, before deploying
- Deploying the bicep file
The code used in this post is available on my GitHub account
To illustrate this point I will use a simple Bicep template. This template deploys a virtual network with subnets. It is a very simple template to illustrate the process.
To start, we need a GitHub repository with a Bicep folder and the main template. An identity is required to deploy the template in Azure.
We need a managed identity
$managedIdentityName = "mi-BicepDemo"
$repoName = "bicep-pipeline"
$githubOrga = "omiossec"
$environmentName = "DevTo-Demo"
$managedIdentity = New-AzUserAssignedIdentity -Name $managedIdentityName -ResourceGroupName managed-identity -Location northeurope
And create a federated identity.
$subjectUri = "repo:$($githubOrga)/$($repoName):environment:$($environmentName)"
New-AzFederatedIdentityCredential -ResourceGroupName managed-identity -IdentityName $managedIdentity.name -Name fed-bicepDemo -Issuer "https://token.actions.githubusercontent.com" -Subject $subjectUri
After that, the environment “DevTo-Demo” must be created in the GitHub repository. To create the environment, go to “Settings” in the top menu and click “Environments”. After creating the environment, secrets need to be added. By using a federated identity you only need to use 3 values, the managed identity Client ID, the Tenant ID, and the target subscription. The managed identity secret is not required.
- AZURE_CLIENT_ID, you can have the value with $managedIdentity.ClientID
- AZURE_TENANT_ID, the value is given by $managedIdentity.TenantId
- AZURE_SUBSCRIPTION_ID, the target subscription ID
But before using the federated identity we need to ensure that mi-BicepDemo has valid access to the subscription, if not the pipeline will not be able to log in, and an error with exit code 1 will be generated.
Assign the contributor role to the mi-BicepDemo identity on the target resource group. The resource group name can be added in the environment variable RESOURCE_GROUP.
Now, we can start to create the workflow. At the root of the GitHub repository, in a .github/workflow folder, create a yaml file, demo-bicep.yaml
name: Bicep Pipeline Demo
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
jobs:
Bicep-demos-login:
name: run azure tests
runs-on: ubuntu-latest
environment: DevTo-Demo
steps:
- name: Checkout
uses: actions/checkout@v4
- name: login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
enable-AzPSSession: true
The pipeline will run at every push made to the main branch.
permissions:
id-token: write
contents: read
This is needed to use the federated identity.
Then we can check out and log in to Azure.
Before deploying the infrastructure to Azure, we want to test the validity of the bicep file.
The easiest way to do it is to use Bicep Lint.
So we can have a step like this.
- name: Bicep Lint
uses: azure/cli@v2
with:
azcliversion: latest
inlineScript: |
az bicep lint --file ./Bicep/main.bicep
But there is a problem with this approach: the pipeline will fail only with the linter error but not with a warning. A malformed resource will trigger an error but exposing a secret will only trigger a warning.
To change the behavior we need to create bicepconfig.json in the same directory as the bicep file, or in a parent directory.
You can find the file here
I have changed a few rules to trigger an error when the location is hard-coded or when variables or parameters are not used.
After testing the bicep file for errors and best practices, the next step is to see what will be changed. Azure Bicep has the option to manage that, what-if.
- name: Bicep What-If
uses: Azure/cli@v2
with:
azcliversion: latest
inlineScript: |
az deployment group create --what-if --name whatif --template-file ./Bicep/main.bicep --resource-group ${{ vars.RESOURCE_GROUP }}
The last step is to deploy the Bicep file. But we want to ensure that there is a review of the what-if output before deploying anything. GitHub has an approval mechanism with environments. An environment can require approval before running a deployment (for GitHub Pro and free, the repository must be public).
In our case, we need to create a new environment, bicep-deploy, with the same variable and secret as the devTo-demo. The option “Required reviewers” must be selected and reviewers added.
We need to configure the managed identity to allow the new environment.
$subjectUri = "repo:$($githubOrga)/$($repoName):environment:$($environmentName)"
New-AzFederatedIdentityCredential -ResourceGroupName managed-identity -IdentityName $managedIdentity.name -Name fed-bicepDemo -Issuer "https://token.actions.githubusercontent.com" -Subject $subjectUri
A new job using this environment needs to be added.
Bicep-deploy:
name: run azure deploy
runs-on: ubuntu-latest
environment: bicep-deploy
needs: Bicep-demos-login
steps:
- name: Checkout
uses: actions/checkout@v4
- name: login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
enable-AzPSSession: true
- name: Bicep deploy
uses: Azure/cli@v2
with:
azcliversion: latest
inlineScript: |
az deployment group create --name whatif --template-file ./Bicep/main.bicep --resource-group ${{ vars.RESOURCE_GROUP }}
The complete workflow is available here
After updating the workflow, you will see the two linked jobs. The instruction “needs: Bicep-demos-login” will make the second job, Bicep-Deploy, after completing the first. As the Bicep-deploy environment requires approval, one reviewer will need to check the output of the what-if before deploying to Azure.
This is a simple example of managing a pipeline for Bicep deployment with testing, review, approval, and deployment. This is a minimalistic approach. For example, if a resource type doesn’t exist, the linter will only show a warning, which is not enough to stop the pipeline. You may need to dig into the linter output.
Top comments (1)
Great post! Thanks for sharing the information about Bicep lint, especially the adjusted settings in the JSON configuration. I’ll definitely try it in my pipelines!