Drücke "Enter", um den Text zu überspringen.

Deploy multiple ARM templates to Azure using PowerShell and GitHub Actions

This is kind of an experiment. Usually, I produce all my content in German language, but to see if englisch content is of higher use for the community, I will give it a try…

In the last days, I dealt a lot with GitHub Actions to find out, how it can be used to deploy Azure ARM templates to the cloud. My last „evolution“ step now – I was working on this because of the very valuable hint of a collegue – is able to deploy ARM templates to all 4 available scope levels:

  • Tenant level
  • Management Group level
  • Subscription level
  • Resource Group level

And it also could be used, even when you don’t use GitHub Actions! But let’s start from the beginning…

A brief history of evolution

In my first appraoch, I have used a simple PowerShell script (based on a Script by Sarah Lean, https://gist.github.com/weeyin83/81e7a7bf3caf3d0bce787db5d562b47e) to just deploy something to Azure using GitHub Actions. This is fast & simple, but not very flexible.

My second approach took me to the new Azure PowerShell Action, where you can just run PowerShell code from within the GitHub Action:

https://github.com/HaikoHertes/ActionsDemo/blob/master/.github/workflows/DeployARMtoAzure.yml

So I used some small code to deploy an ARM template with its parameters file that both reside in an GitHub repo. This is even more easy to use, but still not very flexible, especially, when it comes to multiple ARM templates.

You can still find the code and stuff here:
https://github.com/HaikoHertes/ActionsDemo

Next step was to deploy just a bunch of Templates with their parameters from a repository to become more flexible. Here I wrote a PowerShell script that does the main work:
https://github.com/HaikoHertes/ActionsDemo.v2/blob/master/DeployARMtoAzure.ps1
This script is then just called from within the GitHub Action:

https://github.com/HaikoHertes/ActionsDemo.v2/blob/master/.github/workflows/DeployARMtoAzure.yml#L27

This allready does a good job, so you can fetch everything from here:
https://github.com/HaikoHertes/ActionsDemo.v2

The final solution

But then, a colleague of mine gave me an hint on some new options to not just deploy to Resource Group level, but to higher levels as well. And so I spent a day to investigate and re-write my PowerShell script. And now I can offer a script, that does the job! Let me show you…

You can pick up the script from here:
https://github.com/HaikoHertes/ActionsDemo.v3/blob/master/DeployARMtoAzure.ps1

How the script works

As it is a little more complex, I want to explain what it does and how it works. The script is divided into 5 major parts:

  • Setting the variables and defining the functions
  • Deploying to Tenant level
  • Deploying to Management Group level
  • Deploying to Subscription level
  • Deploying to Resource Group level

If there are no templates for one of these levels or if their folder does not exist, it will just be skipped.

The script expects a defined folder structure:

The names of folders and files can be adjusted in the PowerShell Script:

After all the variables are set, two functions are declared: GetLocation and GetScop. They are made to fetch the location / region from a given directory’s location file or return the default location and to fetch the scope of the deployment either from a scope file or the folder name. Both functions will be used in the later deployments. All files allow the usage of comments that start with the hashtag symbol ‚#‘.

The 4 major parts for the different deployment levels are mostly very similar just with slight differences, thats why I just explain how any of them works.

They first grab all JSON files, that are not in the naming format *parameters.json in any subdirectory (so you can organize your templates by topic or something else and group them into folders). If an order file is given, it will fetch the sorting order from the file (allowing sorting by foldername and filename (default) or filename-only) or just sort by the default order. That way, you can define a deployment order by using proper names for your templates.

Now it will run through the list of templates, getting the scope and location of that templates folder, fetching the tags if any given and then trigger the deployment with any of these Cmdlets, whatever is sufficient for the scope / level:

  • New-AzTenantDeployment
  • New-AzManagementGroupDeployment
  • New-AzSubscriptionDeployment
  • New-AzResourceGroupDeployment

For a given template ABC.json , it allways expects a ABC.parameters.json file next to the template with the parameters. If no parameters are needed, just provide an empty file.

At the end, all templates in all of the 4 levels are deployed 🙂

How to use the script

The script was made to be used with GitHub Actions:

Here it is just called after repo checkout and Azure login. You can get the stuff from here:
https://github.com/HaikoHertes/ActionsDemo.v3/blob/master/.github/workflows/DeployARMtoAzure.yml
https://github.com/HaikoHertes/ActionsDemo.v3

It expects the ARM templates in a subfolder „arm“ within the same repo where the GitHub Action resides.

Be aware that as of today, you need to use ubuntu-20.04 and AzPS 4.1.0 (This is needed for the latest version of the deployment Cmdlets above, and AzPS 4.1.0 is only included in the ubuntu-20.04 container image used by GitHub Actions.

The other option would be to use the script without GitHub Actions and even without any repository. You could just run it locally with a given local folder structure containing the templates and other files. You would just need to add something like

Connect-AzAccount

to do the login. Be aware that you still need Az PS Module version 4.1.0 or higher, which you can install with

Install-Module -Name Az -AllowClobber -MinimumVersion 4.1.0

Setting the proper rights on Azure

To be able to deploy to Azure, you need a user with proper rights. Especially when using GitHub Actions, it is not recommended to use a regular named user, but service principals. In my video (German language, but it shows eveything on english UI) on YouTube, you can see how to do this:

Start at 20:38 to just see the user account part…

You will need something like this:

az ad sp create-for-rbac –name „github“ –role contributor –scopes /subscriptions/YOUR_SUB_ID –sdk-auth

To be able to deploy to Tenant level, you will need the proper rights on root level. Some types of template also need owner rights, especially wheny you want to deploy role assignments or such. Either adjust the above command or (especially, when the user allready exists), extend the rights with somthing like this:

az role assignment create –role „Owner“ –assignee „[USERID]“ –scope „/“

About the different files that can be used

There is several files that can be used to control how the script works:

arm/DefaultLocation.txt

This file defines the default location for any deployment, that needs a location to be set outside the ARM template. It also includes a list of all available location names. Treat this file as mandatory.

arm/ANY_LEVEL/order.txt

This file defines the sorting order and therefor the deployment order within that given level. There is two options:

  • SortByFolderName = Sorting by foldernames and then by filenames (default)
  • SortByFileName = Folder names are ignored, sorting just by templates filenames

The file is optional; if no order is given through a file, the default order will be by foldername first and then by templates filename.

scope.txt next to any template JSON

By default, the deployment scope (i.e. Subscription ID) is fetched from the templates folder ANY_DEPLOYMENT. If needed (i.e. when you want to deploy multiple folders as seperate groups, but into the same scop), you can provide a scope.txt file instead. In here, give ID or name of the scope, both would work.

tags.txt next to any template JSON

You can provide a tags.txt file with the content like

KEY = VALUE

pairs to set tags right on the deployment.

location.txt next to any template JSON

Next to a template file, you can provide a location.txt file that will set the location for the deployment, if needed. If the template itself sets a location, then templates location will be used. If neither template nor location.txt provide a location, but one is needed, the defaultLocation will be used.

(The names are given as they would be with the parameters I provide with my script; they can be changed)

Copyright / legal notice

Copyright 2020 Haiko Hertes

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the „Software“), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED „AS IS“, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.