Azure DevOps Pipeline for Bicep and ARM deployments

Sometimes simple is better. When infrastructure team first start taking on cloud infrastructure they need to get up to speed quickly with infrastructure as code and pipeline deployments. And sometimes we as devops engineers can complicate matters by introducing pipelines that are too complex in nature. This is a simple pipeline that can be used to deploy Bicep and ARM templates to Azure.

Prerequisites

  • Azure DevOps Project
  • Service Connection to your Azure Subscription (make sure this is scoped in at the subscription level)
  • Azure DevOps Environment

Environments

Before you run the pipeline for the first time ensure you configure an environment in your azure devops project and add some approvers to the environment. Check this article to learn how to create an environment in Azure DevOps: Environments Define Approvals and Checks

Pipeline

The code below is a YAML file that represents an Azure DevOps pipeline for Infrastructure as Code (IaC) deployment using ARM templates or Bicep templates. Here's a breakdown of the key components:

  1. The pipeline is named "IaC deployment to RG" followed by the value of the ResourceGroupName parameter.
  2. The pipeline is triggered when changes are made to the main branch.
  3. The pipeline runs on an Ubuntu agent (vmImage: ubuntu-latest).
  4. The pipeline accepts several parameters, including AzureServiceConnection, DeploymentName, PreviewChanges, ResourceGroupName, TemplateFilePath, and TemplateParameterFilePath.
  5. The AzureServiceConnection parameter allows you to specify the Azure service connection to use for the deployment. you can add multiple service connections to your Azure subscriptions in the values section. this will show up as radio buttons in the pipeline ui when you run the pipeline.
  6. The DeploymentName parameter is used to provide a name for the ARM deployment.
  7. The PreviewChanges parameter is a boolean value that enables or disables the what-if preview for the deployment. I would suggest keeping this enabled to allow you to verify the changes before they are deployed.
  8. The ResourceGroupName parameter specifies the target resource group for the deployment.
  9. The TemplateFilePath parameter is the path to the template file (ARM or Bicep) used for the deployment.
  10. The TemplateParameterFilePath parameter is the path to the parameter file (ARM or Bicep) used for the deployment.

The pipeline consists of two stages:

  1. Preview_Deployment stage: This stage is conditionally executed when the PreviewChanges parameter is set to true. It contains a single job named Preview that uses the Azure CLI task to perform a what-if deployment to the specified resource group. The inlineScript property of the task executes the az deployment group what-if command with the provided parameters.

  2. Live_Deployment stage: This stage is executed when the previous stage succeeds. It contains a single deployment job named Deployment. The job is associated with an environment called "IaC Deployment Approval" (which needs to be created in Azure DevOps). The job uses the Azure CLI task to perform the actual deployment to the specified resource group. The inlineScript property of the task executes the az deployment group create command with the provided parameters.

name: IaC deployment to RG ${{ parameters.ResourceGroupName }}

trigger:
- main

pool:
  vmImage: ubuntu-latest

parameters:
  - name: 'AzureServiceConnection'
    type: string
    values: # here you can add all your service connections to your Azure subscriptions.
      - "Dries - VS Enterprise Subscription"
  - name: DeploymentName    # Name for ARM Deployment
    type: string
    default: "<Insert name for deployment>"
  - name: ResourceGroupName     # Resource Group to deploy to
    type: string
    default: '<Insert the target resource group name>'
  - name: TemplateFilePath  # Path to the template file (can be an ARM or Bicep template file)
    type: string
    default: '<Insert path to template file>'
  - name: TemplateParameterFilePath  # Path to the parameter file (can be an ARM or Bicep template file)
    type: string
    default: '<Insert path to parameter file>'


stages:
  - stage: Preview_Deployment
    jobs:
    - job: Preview
      steps:
      - powershell: |
          Write-Output "Listing the Azure DevOps pipeline agent directory with downloaded artifacts"
          Write-Output "--START----------------"
          ls -R $(System.DefaultWorkingDirectory)/
          Write-Output "--END------------------"
        displayName: 'List Artifacts'
        condition: succeeded()
        continueOnError: false
      - task: AzureCLI@2
        displayName: What-If Deploy to Resource Group
        inputs:
          azureSubscription: ${{ parameters.AzureServiceConnection }}
          scriptType: 'pscore'
          scriptLocation: 'inlineScript'
          inlineScript: |
            az deployment group what-if `
              --name '${{ parameters.DeploymentName }}' `
              --resource-group '${{ parameters.ResourceGroupName }}' `
              --template-file '$(System.DefaultWorkingDirectory)/${{ parameters.TemplateFilePath }}' `
              --parameters '$(System.DefaultWorkingDirectory)/${{ parameters.TemplateParameterFilePath }}'

  - stage: Live_Deployment
    jobs:
    - deployment: Deployment
      displayName: Deploy Deployment
      environment: 'IaC Deployment Approval' #this is the environment that you will need to create in ADO
      strategy:
        runOnce:
          deploy:
            steps:
            - checkout: self
            - task: AzureCLI@2
              displayName: Deploy to Resource Group
              inputs:
                azureSubscription: ${{ parameters.AzureServiceConnection }}
                scriptType: 'pscore'
                scriptLocation: 'inlineScript'
                inlineScript: |
                  az deployment group create `
                    --name '${{ parameters.DeploymentName }}' `
                    --resource-group '${{ parameters.ResourceGroupName }}' `
                    --template-file '$(System.DefaultWorkingDirectory)/${{ parameters.TemplateFilePath }}' `
                    --parameters '$(System.DefaultWorkingDirectory)/${{ parameters.TemplateParameterFilePath }}'

Other useful resources