Terraform With Azure DevOps
Introduction
Over the past few weeks I have been helping a couple of my customers take their first steps into the world of DevOps and Infrastructure-as-Code with Terraform.
It’s safe to say there is an array of fantastic content out there already in the community and from the vendors themselves, which have certainly been useful to use with my customers. Some of the content I have been using with my customers is listed below:
- Azure Docs - Terraform on Azure
- Hashicorp Terraform Docs - Get Started Terraform on Azure
- Thomas Thornton - Terraform With Azure DevOps and Build Artifacts
Important: Azure DevOps made changes to free pipeline grants for new organisations back in March 2021, which means you may need to apply to be able to gain access to the free minutes for pipeline agent runs. To do this review the steps instructions and guidance published here.
So What Is This Blog Post About?
Good question. In this blog post I want to share with you how I configure Azure DevOps (Project, Repos, Pipelines, Artifacts, Branch Policies, Variable Groups, Service Connections etc.) to deploy Terraform into Azure. I’ll also show you how I configure Azure resources like Storage Accounts, Key Vaults & Service Principals to handle the remote state for Terraform with Azure DevOps and handling access secrets securely.
This not only applies to Azure and could also re-purposed very easily for any Terraform style deployment using Azure DevOps to orchestrate the process.
Now I won’t be doing this as a long blog post, as many of my other posts are, instead I have recorded this all as a video and put on YouTube for you all to enjoy instead!
I’ve also created an overview diagram to explain the logical flow from someone creating Terraform code to deployment. Which will hopefully help everyone understand all the pieces involved in this process!
So with that, checkout the below diagram, video walk through and beneath all that code snippets and example Terraform files; enjoy!
Process Flow Diagram
Click on the diagram above to open it in full in another tab
The diagram is also available below in Visio & PDF formats:
Video Walk Through
Code Snippets
A YAML extracted version of all of the pipelines shown in the video are available for you to review and import below, if desired. If not, you can copy and paste paths etc.
They are available from: https://github.com/jtracey93/PublicScripts/tree/master/AzureDevOps/TerraformDemoPipelines/ES-Terraform-Community
Import instruction can be found here: https://docs.microsoft.com/en-us/azure/devops/pipelines/get-started/clone-import-pipeline
Terraform Plan Build Pipeline
Terraform Init
terraform init -backend-config="access_key=$(NAME OF KEY VAULT SECRET FOR STORAGE ACCOUNT KEY)"
Terraform Validate
terraform validate
Terraform Plan
terraform plan -input=false -out=tfplan -var="spn-client-id=$(CHANGEME-spn-client-id)" -var="spn-client-secret=$(CHANGEME-spn-secret)" -var="spn-tenant-id=$(CHANGEME-spn-tenant-id)"
Create Archive Step
$(Build.ArtifactStagingDirectory)/$(Build.BuildId)-tfplan.tgz
Publish Artifact Step
# File/Directory To Publish
$(Build.ArtifactStagingDirectory)/$(Build.BuildId)-tfplan.tgz
# Artifact Name
$(Build.BuildId)-tfplan
Terraform Apply Release Pipeline
Extract Archive
$(System.ArtifactsDirectory)/_Terraform Plan/$(Build.BuildId)-tfplan/$(Build.BuildId)-tfplan.tgz
Terraform Init
terraform init -backend-config="access_key=$(NAME OF KEY VAULT SECRET FOR STORAGE ACCOUNT KEY)"
Terraform Apply
terraform apply -auto-approve -input=false tfplan
Terraform File Examples
backend.tf
1terraform {
2 backend "azurerm" {
3 storage_account_name = "tfazdodemostg001"
4 container_name = "terraform-state"
5 key = "tf-azdo-demo.tfstate"
6 }
7}
providers.tf
1terraform {
2 required_providers {
3 azurerm = {
4 source = "hashicorp/azurerm"
5 version = "2.56.0"
6 }
7 }
8}
9
10provider "azurerm" {
11 features {}
12 subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
13 client_id = var.spn-client-id
14 client_secret = var.spn-client-secret
15 tenant_id = var.spn-tenant-id
16}
variables.tf
1variable "spn-client-id" {}
2variable "spn-client-secret" {}
3variable "spn-tenant-id" {}