In this blog I am going to write about how you can automatically validate Bicep template changes inside a pull request in Azure DevOps. This validation step can help identify errors written in a template or parameter file. These errors can be syntactical or input-based errors. The techniques used in this blog are: Azure CLI, Azure DevOps, Azure Bicep and YAML.
Validation placement
Before I go in-depth let’s see where the Bicep validation will take place. Let’s take a look at the development flow:

- There is an engineer who writes code in their favourite IDE.
- The engineer works on a development branch (feature branch).
- When the engineer is done a pull request has to be created:
- In this pull request there is an automated pipeline that is triggered on pull request creation. This pipeline validates the Bicep template and parameter file found in the pull request. This pipeline triggers after every change in the PR.
- Two other engineers check the code in the pull request and provide feedback when necessary.
- After approval the PR will be merged with the main branch.
- The reviewed Bicep template and parameter file are being deployed.
In this blog I am going to focus on the “Automated Bicep Validation” step.
Repository
To work with an example I am going to create a virtual network (VNet). To create a VNet you need a Bicep template where the VNet is defined and a parameter file to populate the Bicep input parameters:
Bicep template example (virtualNetwork.bicep)
param parVnetName string | |
param parAddressSpace string | |
param parSubnet string | |
resource resVirtualNetwork 'Microsoft.Network/virtualnetworks@2015-05-01-preview' = { | |
name: parVnetName | |
location: 'westeurope' | |
properties: { | |
addressSpace: { | |
addressPrefixes: [ | |
parAddressSpace | |
] | |
} | |
subnets: [ | |
{ | |
name: 'my-subnet' | |
properties: { | |
addressPrefix: parSubnet | |
} | |
} | |
] | |
} | |
} |
Parameters input (virtualNetwork.parameters.json)
{ | |
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", | |
"contentVersion": "1.0.0.0", | |
"parameters": { | |
"parVnetName": { | |
"value": "validation" | |
}, | |
"parAddressSpace": { | |
"value": "10.10.0.0/22" | |
}, | |
"parSubnet": { | |
"value": "10.10.50.0/24" | |
} | |
} | |
} |
Add a new parameter
If you want to deploy the virtual network in multiple regions you need to parameterize the location input. To add the location parameter input you need to create a feature branch called something like add-location
. In this branch you add the code in the Bicep template and parameter file:
param parLocation string | |
resource resVirtualNetwork 'Microsoft.Network/virtualnetworks@2015-05-01-preview' = { | |
location: parLocation | |
} |
"parLocation": { | |
"value": "10.10.50.0/24" | |
} |
Now you have a change ready for a pull request.
Pipeline
You want the pipeline to run every pull request creation or every pull request change. It will run the validation using an Azure CLI command. The pipeline is written in YAML and will contain two jobs:
- Checkout
- Validation_Step_PR
jobs: | |
- job: Checkout | |
steps: | |
- checkout: git://bicep-validation/bicep-validation@$(Build.SourceBranch) | |
- task: PublishBuildArtifacts@1 | |
inputs: | |
pathToPublish: $(Build.SourcesDirectory) | |
artifactName: drop | |
- job: Validation_Step_PR | |
displayName: "Validating Bicep in PR" | |
dependsOn: Checkout | |
steps: | |
- task: DownloadPipelineArtifact@2 | |
inputs: | |
artifact: drop | |
- task: AzureCLI@2 | |
displayName: Validate | |
inputs: | |
azureSubscription: publish-my-iac | |
scriptType: pscore | |
scriptLocation: inlineScript | |
inlineScript: > | |
az deployment group validate | |
--resource-group rg-we-prod-validation | |
--template-file Infrastructure/Templates/virtualNetwork.bicep | |
--parameters Infrastructure/Parameters/virtualNetwork.parameters.json |
Job: Checkout
In this job the pipeline does a checkout at bicep-validation
repository. The crucial step of the checkout process is to refer to the pull request source branch. Azure DevOps has a built-in variable called Build.SourceBranch
which targets the source branch of the pull request. In this case the feature branch add-location
is going to be checked-out. If you don’t specify this variable the default branch is going to be checked-out. When the checkout is done the artifacts are being published with the name drop.
Checkout URL segments explained: git://<Azure DevOps Project Name>/<Git repo name>@<Branch>
Job: Validation_Step_PR
In this job the pipeline downloads the published artifacts. After that the validation step comes into play. I use the built-in Azure CLI validation tool to validate the Bicep template. Add the AzureCLI@2
task and set the scriptLocation
property to inlineScript
. You can also use a value scriptPath
to refer to a file instead of inline.
The inlineScript
runs an Azure CLI command az deployment group validate
with the following properties:
resource-group
→ rg-we-prod-validationtemplate-file
→ path to virtualNetwork.bicepparameters
→ path to virtualNetwork.parameters.bicep
This is everything you need to create to validate our Bicep using a pipeline. Commit and push the YAML file and create the pipeline. I named mine BicepTemplateValidation.

Next you need to set up a build validation policy
so the pipeline triggers on every pull request.
Branch policy
A branch policy protects important branches. You want to protect the main branch and enforce a validation pipeline run before merging any feature branches. To set a branch policy you must be a member of the Project Administrator group or you need edit policy permissions.
Under Repos go to Branches. On this screen you see all active branches. Hover over the main branch (or over another default branch) to see the ellipsis (“three dots”) on the right, click on these and go to Branch policies.

On the Branch policies screen add the following setting by pressing the “+” button:
- Build validation

Refer to the validation pipeline you created. Set the build expiration to “immediately” so the pipeline always triggers on new pushes to the pull request, also after the pull request has been created. Also the trigger is set to automatic so the pipeline always runs a pull request is created.

Pull request
Let’s merge the location parameter changes from the feature branch to the main branch using a pull request and see what happens with the pipeline.

You can see the build validation policy is being enforced. The validator pipeline runs when the pull request is created or when new changes are pushed in the pull request.
Pipeline result
If you look at the pipeline output you see the added parameter parLocation
in our validation. This is the change we made in the feature branch, which is being validated in the pull request.

In the overview of the pull request it shows that the automated validation pipeline has run successfully and the PR is now able to be completed and merged to main.

Let’s take a look at what happens if you make a typo in a parameter. For example, you make a typo in the CIDR notations of the subnet address. Instead of assigning a CIDR-block of /24
I assigned a CIDR-block of /245
.
Make a new commit and push it to the pull request. It reruns the validator pipeline and validates the newly made changes. In the overview it shows that the validation has failed, which means that the mistake has been automatically detected.


Conclusion
This is one way to validate your Bicep template and parameter files. I like this way of validation because when you work with large and complex Bicep files small errors like typos will be picked up automatically. This allows the reviewers to focus on other things in the pull request.
You can extend the YAML file to support multiple Bicep validations using a loop or do checkouts based on your Git repository structure.
The Azure Pipeline YAML is uploaded as a GitHub gist: https://gist.github.com/johnlokerse/033a92ad85dde67565fdd97f4de87321.
One thought on “Azure Bicep validation in a pull request with Azure DevOps”