Deploying ARM Templates using Azure DevOps Pipelines

Deploying ARM Templates and want to deploy using Azure DevOps Pipelines? Lets have a look in this blog post on how to achieve this!

What is Azure DevOps?

Deploying resources already into Azure; you probably already have came across using Azure DevOps, it is a hosted service by Microsoft that provides an end-to-end DevOps toolchain for developing and deploying software, along with this – it is a hosted service to deploy CI/CD Pipelines

Initial requirements before you can begin deploying

There are some prior requirements you need to complete before we can get deploying ARM template using Azure DevOps.

These are:-

  • Azure DevOps Project
  • Azure Service Principal
  • Sample ARM code

Lets have a look at each of these requirements; I will include an example of each and how you can configure.

Azure DevOps Project

Deploying ARM templates using Azure DevOps, requires some sort of project; in this blog I will create a new project

This is documented already by Microsoft here, I recommend this guide to show you how to setup a DevOps Project similar to mine below

The DevOps Project in my example will be called TamOpsARMTemplates as below

Azure Service Principal

A Service Principal (SPN) is considered a best practice for DevOps within your CI/CD pipeline. It is used as an identity to authenticate you within your Azure Subscription to allow you to deploy the relevant ARM code.

In this blog, I will show you how to create this manually (there is PowerShell / CLI but within this example I want you to understand the initial setup of this)

To begin creation, within your newly created Azure DevOps Project – select Project Settings

Select Service Connections

Select Create Service Connection -> Azure Resource Manager -> Service Principal (Automatic)

For scope level I selected Subscription and then entered as below, for Resource Group I selected tamopsarm which I created earlier

Once created you will see similar to below

You can select Manage Service Principal to review further

When creating this way, I like to give it a relevant name so I can reference my SPN easier within my Subscription. This is done within “Manage Service Principal”

Manage -> Branding and change Name as below

You can also reference your SPN easier if you want to give it further IAM control to your subscription, in this setup I also give the SPN “contributor” access to my subscription.

Documented role assignment here by Microsoft

Sample ARM Code

The below ARM template & parameters file will create a virtual network with two subnets:-

vnet.json

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "vnetName": {
        "type": "string",
        "metadata": {
          "description": "VNet name"
        }
      },
      "vnetAddressPrefix": {
        "type": "string",
        "metadata": {
          "description": "Address prefix"
        }
      },
      "subnet1Prefix": {
        "type": "string",
        "metadata": {
          "description": "Subnet 1 Prefix"
        }
      },
      "subnet1Name": {
        "type": "string",
        "metadata": {
          "description": "Subnet 1 Name"
        }
      },
      "subnet2Prefix": {
        "type": "string",
        "defaultValue": "10.0.1.0/24",
        "metadata": {
          "description": "Subnet 2 Prefix"
        }
      },
      "subnet2Name": {
        "type": "string",
        "defaultValue": "Subnet2",
        "metadata": {
          "description": "Subnet 2 Name"
        }
      },
      "location": {
        "type": "string",
        "defaultValue": "[resourceGroup().location]",
        "metadata": {
          "description": "Location for all resources."
        }
      }
    },
    "variables": {},
    "resources": [
      {
        "type": "Microsoft.Network/virtualNetworks",
        "apiVersion": "2020-05-01",
        "name": "[parameters('vnetName')]",
        "location": "[parameters('location')]",
        "properties": {
          "addressSpace": {
            "addressPrefixes": [
              "[parameters('vnetAddressPrefix')]"
            ]
          }
        },
        "resources": [
          {
            "type": "subnets",
            "apiVersion": "2020-05-01",
            "location": "[parameters('location')]",
            "name": "[parameters('subnet1Name')]",
            "dependsOn": [
              "[parameters('vnetName')]"
            ],
            "properties": {
              "addressPrefix": "[parameters('subnet1Prefix')]"
            }
          },
          {
            "type": "subnets",
            "apiVersion": "2020-05-01",
            "location": "[parameters('location')]",
            "name": "[parameters('subnet2Name')]",
            "dependsOn": [
              "[parameters('vnetName')]",
              "[parameters('subnet1Name')]"
            ],
            "properties": {
              "addressPrefix": "[parameters('subnet2Prefix')]"
      }
        }
      ]
      }
    ]
      }
        
    

vnet.parameters.json

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        
        "vnetName": {
            "value": "tamops-vnet"
        },
        "vnetAddressPrefix": {
            "value": "10.0.0.0/16"
        },
        "subnet1Prefix": {
            "value": "10.0.0.0/24"
        },
        "subnet1Name": {
            "value": "Subnet1"
        },
        "subnet2Prefix": {
            "value": "10.0.1.0/24"
        },
        "subnet2Name": {
            "value": "Subnet2"
        }
        
  }
}

Deploying ARM template using Azure DevOps

The initial requirements now configured, time to setup Azure DevOps to deploy the ARM template into Azure.

For the deployment, we will use Azure Pipelines (YAML) with the task:- AzureResourceManagerTemplateDeployment@3

The below is the .yaml file that will be used referencing the above task

name: $(BuildDefinitionName)_$(date:yyyyMMdd)$(rev:.r)

trigger: none

pr: none

stages :        
  - stage: arm_template_deploy
    jobs:
      - job: arm_template_deploy
        steps:
              - checkout: self

              - task: AzureResourceManagerTemplateDeployment@3
                inputs:
                  deploymentScope: 'Resource Group'
                  azureResourceManagerConnection: 'tamopsarm'
                  subscriptionId: 'Your-Subscription-ID'
                  action: 'Create Or Update Resource Group'
                  resourceGroupName: 'tamops-arm-template'
                  location: 'eastus2'
                  templateLocation: 'Linked artifact'
                  csmFile: '$(System.DefaultWorkingDirectory)/template/vnet.json'
                  csmParametersFile: '$(System.DefaultWorkingDirectory)/template/vnet.parameters.json'
                  deploymentMode: 'Incremental'


Once the above has been uploaded to the Azure DevOps Repo, we can create a pipeline

Select Pipelines -> New Pipeline

Select the repo and .yaml file

Now save and run pipeline

Review the pipeline output:-

From the AzureResourceManager task, we can see the template has been deployed successfully

ARM Service Connection deployment scope - Resource Group
Checking if the following resource group exists: tamops-arm-template.
Resource group exists: true.
Creating deployment parameters.
The detected encoding for file '/home/vsts/work/1/s/template/vnet.json' is 'utf-8'
The detected encoding for file '/home/vsts/work/1/s/template/vnet.parameters.json' is 'utf-8'
Starting template validation.
Deployment name is vnet-20201117-095544-0bfc
Template deployment validation was completed successfully.
Starting Deployment.
Deployment name is vnet-20201117-095544-0bfc
Successfully deployed the template.

Within Azure Portal, Vnet has been created

All code used in this blog post found in this GitHub repo, thank you for reading!

2 comments

  1. You sir, I believe have shown the only working example of using this Task type, with a self hosted azure repo that actually works. Thank you. For me the key point is you need to include checkout: self before running it when using stages

Leave a Reply