Lint Azure Bicep templates in Azure DevOps

Stable, scalable and secure infrastructure as code is important to have a performing Azure environment. As organisations adopt Infrastructure as Code (IaC) to automate and manage their cloud resources, the need for maintaining code quality and enforcing best practices becomes important. This is where Azure Bicep linting comes into play, especially when integrated with Azure DevOps build validation.

What is linting in general?

Linting is the process of running a program that analyses code for potential errors, stylistic issues, and deviations from coding standards or best practices. It helps developers identify problems such as syntax errors, bugs, stylistic errors, and suspicious constructs that could lead to future issues, without executing the code. Linting tools are used to improve code quality, maintain consistency across a codebase, and ensure adherence to coding guidelines, making the code more reliable and easier to maintain.

Azure Bicep linter

The Azure Bicep linter is designed to catch Bicep specific syntax errors and best practice violations. The linting functionality helps to enforce Bicep coding standards and checks your Bicep template against Microsoft-defined best practices. These Bicep best practices are defined in the bicepconfig.json file and can be customised.

Linter checks

The set of rules for the linter to evaluate can be found in the bicepconfig.json file. If you don’t have a bicepconfig.json in your project, you can create one and add the following content:

{
"analyzers": {
"core": {
"enabled": true,
"rules": {
... rules here ...
}
}
}
}

As you see in the above configuration file the analyser can be enabled or disabled. Also, there is a property “rules” in which you can define which rules have to be analysed. There’s a list of rules available on the Microsoft Bicep site here which are built-in.

Let’s fill the rules property with the rule no-unused-params:

{
"analyzers": {
"core": {
"enabled": true,
"rules": {
"no-unused-params": {
"level": "error"
}
}
}
}
}

When you add the rule you can choose the diagnostic level. The diagnostic level determines the way the linter reacts during either build-time or when authoring a Bicep template when violation is found. The available levels you can choose are:

Diagnostic levelBuild-time behaviourEditor behaviour
ErrorShows errors in command-line output and your builds will fail.Shows a red squiggly line in your code editor.
WarningShows warnings in the command-line output, but does not fail the build.Shows a yellow squiggly line in your code editor.
InfoNothing happens during build-time.Shows a blue squiggly line in your code editor.
OffLinting is fully suppressedLinting is fully suppressed

In the example you see the no-unused-params configured with diagnostic level error. This is how it looks within the editor:

Azure Bicep linter in Visual Studio Code (editor behaviour)

Lint your Azure Bicep templates

Incorporating linting as part of static code analysis during your development cycle helps catch errors in your infrastructure as code. Ideally, this should be done as early as possible! Azure DevOps can assist by performing these checks regularly, for example, by triggering a build validation to lint your Bicep templates when a pull request is opened. This occurs before the code merges with a critical branch, such as the main branch.

In this blog, I will focus on the Azure CLI tooling, but there are several Bicep related linting tools available. Choose what suits best for your workload:

Integrate linter in an Azure Pipeline

Before you can configure build validation, you need to create an Azure pipeline. After creating the pipeline, it will be configured for build validation. The purpose of this pipeline is to execute the Bicep linter command az bicep lint --file <filePath> to lint Azure Bicep templates. Let’s take a look at the YAML file:

trigger: none
parameters:
- name: OnlyLintChangedFiles
type: boolean
default: true
displayName: "Only lint changed files"
pool:
vmImage: windows-latest
steps:
- task: PowerShell@2
displayName: "[Lint] Azure Bicep Template Files"
inputs:
targetType: "inline"
pwsh: true
script: |
$lintChangedFiles = ${{ parameters.OnlyLintChangedFiles }}
$bicepTemplates = @()
$failedLints = @() # Array to hold paths of failed lints
if ($lintChangedFiles) {
$bicepTemplates = git diff --name-only --diff-filter=AM $(git merge-base HEAD refs/heads/main) HEAD | Select-String -Pattern "\.bicep$"
} else {
$bicepTemplates = Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse -Filter *.bicep | Select-Object FullName -ExpandProperty FullName
}
Write-Host "Found $($bicepTemplates.Count) Bicep templates to lint:"
$bicepTemplates
foreach ($path in $bicepTemplates) {
Write-Host "[Lint] Linting Bicep template $path"
$output = az bicep lint --file $path --only-show-errors 2>&1
if ($LASTEXITCODE -ne 0) {
$failedLints += [PSCustomObject]@{
Path = $path
ErrorMessage = $output
}
}
}
if ($failedLints.Count -gt 0) {
Write-Host "##[error][Failed] Linting failed for the following Bicep templates:"
[System.Environment]::NewLine
foreach ($failedLint in $failedLints) {
Write-Host "Path: $($failedLint.Path)"
Write-Host "Error: $($failedLint.ErrorMessage)"
[System.Environment]::NewLine
}
exit 1
} else {
Write-Host "All Bicep templates passed linting."
}

This YAML file is designed orchestrate the validation Azure Bicep template files. It contains contains the PowerShell task, which will be used to trigger az bicep lint.

Before triggering the lint command, there are some checks that are configurable. You have the option to check either only the files changed in the pull request or to perform a full check on every file in the Build.SourcesDirectory. If the OnlyLintChangedFiles parameter is set to true, it will run a Git command to determine which files have been changed. Following that, a regex is used to identify files that end with the .bicep extension.

If OnlyLintChangedFiles is set to false, it will retrieve all files in the Build.SourcesDirectory. This is a predefined Azure DevOps variable used to refer to the local path on the agent where your source code files are downloaded.

Finally, when the bicepTemplates array is filled with the Bicep templates that need to be linted, it loops through the array to lint the files one by one. If the linter detects an error, an object with the properties Path and ErrorMessage is added to the failedLints array. The output of this array is displayed in the pipeline for verbosity.

Pipeline output

In the image below you see the output of the Azure Pipeline. In the image you see logging regarding how many files are found that have to be linted and the actual linting action. When failed lints are found it prints the Path and the ErrorMessage for each Bicep template.

Azure Pipeline output when the linter detected an error

Setup Build Validation to run the linter in a pull request

A build validation can be configured in branch policy in Azure DevOps. A branch policy help to protect branches to enforce code quality standards. Most of the time the branch policy is configured on the main branch which is the most important branch that you want to protect at all costs.

To configure a branch policy follow these steps:

  1. Go to your repository and click on Branches.
  2. Hover over the main branch to see the ellipsis (three dots) on the right, click on these dots.
  3. It opens a dropdownmenu and click on Branch policies.

On the Branch Policies screen add a build validation:

On the Build Validation configuration screen refer to the Bicep linter pipeline. You can choose to keep the default configuration or update it to your liking. After saving this will be the output:

Now each pull request created to the main branch will trigger the Azure Bicep template linter pipeline:

Conclusion

In conclusion, Azure Bicep linting combined with Azure DevOps Build Validation provides a robust and efficient way to improve code quality, maintain consistency, and enforce best practices in your Bicep infrastructure-as-code projects.

The Azure Pipeline used in this blog is ready to be implemented in your projects. Just add it as a pipeline in Azure DevOps project and use it in the build validation. Happy linting!

Leave a comment