Terraforms Write-Plan-Apply but with Azure Bicep
How to run an infrastructure-as-code/devops pipeline with Azure Bicep that follows the Terraform write-plan-apply process
Terraform
Terraform is the industry standard for infra-as-code. It’s a domain-specific language (DSL) that enables us to write configurations for infrastructure in human-readable code. It follows a simple write-plan-apply process when deploying infrastructure and allows for validation and reviews before the final deployment.
Azure Bicep
Azure Bicep is Microsoft’s own DSL that is based on their ARM (Azure Resource Manager). The goal with Bicep is to make resource deployment using infra-as-code more accessible by offering a more user-friendly language. There is no pre-described way on how to use Bicep templates as it is with the write-plan-apply process in Terraform. Azure Bicep has it’s own command line extension: az bicep
Write-Plan-Apply
Terraform works with a state file where the state of the deployment is tracked. With every deployment the state file is updated to include the latest changes. On the contrary Azure Bicep doesn’t use a state file but instead checks the current state of the resources in Azure.
Bicep files are idempotent, which means you can deploy the same file many times and get the same resource types in the same state. You can develop one file that represents the desired state, rather than developing lots of separate files to represent updates.
The pipeline
The write-plan-apply process allows us to review our infrastructure changes before we commit to them. By creating automated Terraform pipelines we can update our infrastructure with every pull request and have reviewers approve the changes.
To recreate the same in Azure Bicep we will have to take advantage of the az cli and its extension az bicep.
Write:
This step doesn’t require any custom code. In the write step we update the configuration files and check them into source control.
Plan:
This step is more tricky. First we want to validate our configuration files. We can do this using az bicep build. Even though the name of this command is confusing it actually validates the bicep files.
Secondly we want to see what will happen if we deploy the validated files. There is no command in the az bicep cli yet but there is another option to do this. We can use the az deployment what-if option which does exactly what we need it to do.
Apply:
Terraforms apply step asks the reviewer to approve the changes before the deployment starts. We can add this feature manually to our pipeline. After the az deployment what-if step we add a reviewer step. Once this is approved the deploy task is run.
The code:
# Deployment pipeline
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
stages:
- stage: validate_and_plan_stage
displayName: Validate and plan deployment
jobs:
- job: build_and_deploy_job
condition: or(eq(variables['Build.Reason'], 'Manual'), eq(variables['Build.Reason'], 'PullRequest'))
displayName: Validate and Plan Job
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
submodules: true
- task: AzureCLI@2
displayName: Validate template files
condition: always()
inputs:
azureSubscription: $(principal)
scriptType: pscore
scriptLocation: inlineScript
inlineScript: |
az bicep build --file "bicep/main.bicep"
- task: AzureCLI@2
displayName: Plan deployment
condition: always()
inputs:
azureSubscription: $(principal)
scriptType: pscore
scriptLocation: inlineScript
inlineScript: |
az deployment group what-if --subscription "$(subscriptionId)" --resource-group "$(rg)" --template-file "bicep/main.bicep" --parameters "bicep/main.bicepparam"
- stage: review_by_user
displayName: Review stage
jobs:
- job: waitForValidation
displayName: Wait for external validation
pool: server
timeoutInMinutes: 1440 # job times out in a day
steps:
- task: ManualValidation@0
timeoutInMinutes: 1440 # task times out in 1 day
inputs:
notifyUsers: |
$(user-email)
instructions: 'Please validate the build configuration and resume'
onTimeout: 'reject'
- stage: deploy_resources
displayName: Deploy resources
jobs:
- job: deploy_job
condition: or(eq(variables['Build.Reason'], 'Manual'), eq(variables['Build.Reason'], 'PullRequest'))
displayName: Build and Deploy Job
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
submodules: true
- task: AzureCLI@2
displayName: Deploy resources
condition: always()
inputs:
azureSubscription: $(principal)
scriptType: pscore
scriptLocation: inlineScript
inlineScript: |
az deployment group create --subscription "$(subscriptionId)" --resource-group "$(rg)" --template-file "bicep/main.bicep" --parameters "bicep/main.bicepparam"
To run the above code you will need an Azure DevOps project, a service principal with Contributor or Owner rights on the resource group level and someone who can approve the deployment.
Add the following variables to your Azure devops pipeline:
- subscriptionId: Id of the subscription
- rg: Name of the resource group
- user-email: Email of the user who has to review the deployment
- principal: Name of the service connection
The main bicep file is located at bicep/main.bicep and the parameters file at bicep/main.bicepparam.
The pipeline run:
This is how the pipeline run will look like when all the different jobs have succeeded.
Summary
By using the az bicep cli and the az deployment group what-if option we recreated a version of the Write-Plan-Apply workflow from Terraform in Azure Bicep. Using the above template we can now automatically validate, plan, review and deploy our resources to Azure using Bicep.
If you found this article useful, please follow me.