Azure DevOps Terraform Pipeline

In my last post I wrote about how I created an Azure DevOps pipeline using Ansible in a more “Terraform” way, however, although I have used Terraform and Azure DevOps quite a bit I realised I didn’t really have a template for a pipeline.

Before we look at the pipeline itself I should point out that is uses the the Terraform Azure Pipeline extension by Microsoft DevLabs from the Visual Studio Marketplace, if you are following along with this post please ensure that the extension is installed in your organisation before proceeding.

Now we have that out of the way, and before I breakdown the azure-pipelines.yml file, lets quickly get an overview of what the tasks which are running in the pipeline;

As you can see from the flow of tasks above I am creating the storage account used to store the Terraform state file as part of the pipeline itself rather than doing manually outside of the pipeline. In nearly all of my Azure DevOps pipelines I see the following variable as SUBSCRIPTION_NAME which contains the name of the service connection that should be used to connect to Azure, wherever you see $(SUBSCRIPTION_NAME) in the following tasks that is referring to the service connection.

The azure-pipelines.yml file starts of by defining the trigger, in this case it is triggered each time something is pushed to the master branch:

Next, I define some variables, apart from tf_version which fines which version of Terraform I want to install the rest are to do with the Terraform statefile resources which need to be configured before I run Terraform:

Then we decide on the image used for the pipeline run, as you may have guessed, I am using Linux:

Now that the basics for the pipeline are covered I can start to define some tasks, beginning with installing Terraform:

As you can see, I am using the terraformInstaller@0 task from the Terraform DevOps extension along with the $(tf_version) which was defined at the top of the file. Now that Terraform is installed I need to either create the storage account, or if it already exists gather some facts. To do this I am using the AzureCLI@2 task:

As you can see I am running a quite a few commands, these do the following:

  1. Sets an environment variable containing the tags defined in the pipeline variables
  2. Creates a Resource Group and tags it
  3. Creates the Azure Storage Account and tags it
  4. Sets an environment variable containing the Storage Account key
  5. Creates a container in the Azure Storage Account which has just been created using the key gathered in the previous step
  6. Adds the Azure Storage Account key as a pipeline variable so that we can use it in the next task

If the Resource Group, Azure Storage Account and container already exist then we still need the Azure Storage Account key so this task needs to be executed during each pipeline run as the following task needs to interact with the Azure Storage account:

Here I am running terraform init and passing all of the variables which tell Terraform how to configure the AzureRM backend service with the details of the Azure Storage account I configured in the previous task. Once the terraform init has been executed we do not need to pass the AzureRM backend service details again. The final two tasks plan and apply the Terraform configuration:

As you can see I am providing rather than using backendServiceArm I am using environmentServiceNameAzureRM, this means that if I wanted I could store my Terraform statefile(s) in a completely separate Azure Subscription from where I am deploying my resources should I want to.

The Terraform files themselves in this project are pretty boring as I don’t want it to do anything exciting, the main.tf looks like the following:

With the variables.tf looking like:

The pipeline run looks like the following:

You can find the repo containing all of the code above at https://github.com/russmckendrick/DevOpsTerraformPipeline.

Geek, Lover Of Shiny Things and Grump.