Introduction

When working with Bicep for Infrastructure-as-Code (IaC), ensuring the quality and reliability of your templates is critical. The Bicep linter, integrated into the Bicep CLI, helps developers identify best practice violations and syntax issues early. However, a common question arises: Should you include a dedicated linting stage in your CI/CD pipeline, or is it sufficient to rely on the integration provided by the what-if deployment stage?

This blog post explores the pros and cons of using a separate linting stage, when it might be necessary, and provides an example of a scenario where it proves indispensable.


Understanding Bicep Linting

The Bicep linter evaluates your templates for potential issues, such as:

  • Hardcoded environment values.
  • Missing required properties.
  • Incorrect naming conventions.
  • Violations of best practices.

The linter can be configured with a bicepconfig.json file to adjust rule severities or suppress specific warnings.

How Linting Works in the Bicep CLI

The linter runs automatically during a bicep build or bicep what-if command, meaning that linting errors will surface in the what-if stage of your Azure DevOps pipeline. However, this approach has limitations, which we will address.


Benefits of a Separate Linting Stage

While the what-if stage catches linting errors, adding a dedicated linting stage offers several advantages:

1. Faster Feedback Loops

  • Linting is lightweight and runs faster than the what-if operation, which performs more complex tasks like evaluating resource state changes.
  • Developers receive feedback earlier in the pipeline, reducing turnaround times for fixes.

2. Improved Debugging

  • Consolidating linting issues into a separate stage makes it easier to identify and fix specific problems.
  • Developers can focus exclusively on resolving linting errors without being overwhelmed by deployment-related logs.

3. Quality Gate Enforcement

  • A linting stage can act as a pre-deployment quality gate, ensuring that templates meet organizational standards before proceeding to resource evaluation or deployment.
  • Teams can enforce coding standards rigorously without depending solely on the what-if output.

4. Separation of Concerns

  • Separating linting from resource state evaluation aligns with the principle of decoupling. It ensures that linting is handled independently, simplifying troubleshooting and pipeline logic.

When Is a Separate Linting Stage Essential?

A dedicated linting stage becomes critical in the following scenarios:

1. Large Teams with Strict Governance

Organizations with strict governance policies may mandate a separate linting stage to ensure that all templates comply with security, naming, and configuration standards before deployment.

2. Early Detection of Configuration Errors

For example, consider a situation where hardcoded environment-specific URLs are prohibited. If this is caught during the what-if stage, it would likely delay debugging and slow the feedback loop. By catching this issue earlier in a linting stage, developers can resolve it immediately.

3. Modular Pipelines

When deploying modular templates, such as parent-child Bicep files, running linting independently for each module ensures that errors in one module do not cascade into others.


Pipeline Example: Adding a Linting Stage

Below is an example Azure DevOps YAML pipeline that includes a separate linting stage before proceeding to the what-if stage:

trigger:
  branches:
    include:
      - main
      - feature/*

stages:
  - stage: Linting
    displayName: "Lint Bicep Files"
    jobs:
      - job: Lint
        displayName: "Run Bicep Linter"
        steps:
          - task: UseBicepCLI@0
            displayName: "Install Bicep CLI"
            inputs:
              version: 'latest'
          - script: |
              bicep lint --file ./main.bicep
            displayName: "Run Linter"
  
  - stage: WhatIf
    dependsOn: Linting
    displayName: "Azure What-If Deployment"
    jobs:
      - deployment: WhatIf
        environment: 'Azure'
        strategy:
          runOnce:
            deploy:
              steps:
                - task: AzureCLI@2
                  displayName: "Run What-If"
                  inputs:
                    azureSubscription: '<YourSubscription>'
                    scriptType: bash
                    scriptLocation: inlineScript
                    inlineScript: |
                      az deployment group what-if \
                        --resource-group '<YourResourceGroup>' \
                        --template-file ./main.bicep \
                        --parameters @parameters.json

Example Scenario: Enforcing Hardcoded Value Restrictions

Context

Imagine a scenario where your organization prohibits hardcoded URLs for database connections, such as database.windows.net. Without a linting stage, such violations might only surface during the what-if stage, delaying remediation.

Solution

Adding a dedicated linting stage ensures that violations are caught earlier. The linter will flag the issue, allowing developers to resolve it before resource evaluation begins.

{
  "analyzers": {
    "core": {
      "rules": {
        "no-hardcoded-environment-urls": {
          "level": "error"
        }
      }
    }
  }
}

Addressing Common Concerns

Isn't Real-Time Linting in VS Code Enough?

While real-time linting in VS Code is a powerful tool during development, it has limitations:

  • Local Environment Variances: Developers may have different configurations or disabled linting rules, leading to inconsistencies.
  • Missed Checks: A developer might overlook warnings or fail to run the linter before committing changes.
  • Pipeline-Level Assurance: A centralized linting stage ensures that every commit is validated according to the latest rules.

Conclusion

While the Bicep CLI and what-if stage provide built-in linting capabilities, adding a dedicated linting stage to your CI/CD pipeline offers significant advantages, particularly for large teams, complex deployments, or organizations with strict compliance requirements. It reduces feedback loops, improves debugging, and enforces quality gates effectively.

For smaller teams or simpler projects, relying solely on the what-if stage may suffice. However, for most production-grade pipelines, investing in a separate linting stage is a best practice that pays dividends in maintainability and efficiency.

What do you think? Share your experiences with Bicep linting in the comments below!