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:

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" {}