Enhance ARM deployments with PowerShell

Disclaimer: Cloud is very fast moving target. It means that by the time you’re reading this post everything described here could have been changed completely Smiley. Hopefully some things still apply to you! Enjoy the ride!

Azure Resource Manager templates (ARM) is excellent way to deploy your infrastructure to Azure (see example from my previous blog post to get started). However sometimes it might feel a bit of static when you need to build infrastructure that uses some shared elements or something that needs to be injected at runtime to the deployment.

But no worries PowerShell comes to the rescue! And by that I mean that we need to leverage scripting capabilities of PowerShell in our deployment pipeline. We’re not going to be in using pure static ARM templates and we’re not going to be using pure PowerShell either in our deployment but instead we want to pick best of both worlds. This lets us to get dynamic scripting characteristics to our deployment.

One very common scenario which people first bump into is the use of Key Vault in deployments. You of course want to securely manage your app secrets (source control is big no-no Smiley) but it’s a bit of challenge to manage it. If you look at the Quick start ARM templates on GitHub or documentation about Key Vault (example one) you get instructed to use Key Vault as static reference which will not work in practice when you need to work with real multi-environment systems. Those static references of course work when you have single simple environment but when you want to deploy this to n environments then it’s not going to scale. And this is the part where I’ll use PowerShell magic to make it multi-environment friendly.

So let’s use exactly same deployment method as I had in my previous blog post. First I have some source assets:
Source assets of our solution
Second I have CI:
Third I have CD:
CD Overview
Now this is the part where we must pay a bit more attention. Change to the previous post is that now my process has two scripts:

deploy-initial.ps1: This is responsible of creating resource group and Key Vault if it doesn’t exist.
deploy.ps1: This is responsible of creating resources to the existing resource group and leveraging existing information from Key Vault.

Idea behind above split is to make sure that we create Key Vault resource first and dependent resources afterwards. And when the Key Vault is created it is also initialized with required contents. After that initial creation Key Vault secrets are managed directly to Key Vault (in another words outside from this script). This model also allows you to create the Key Vault beforehand in you want to. It would mean that deploy-initial.ps1 wouldn't actually do anything since resources have been already earlier created.

Of course we need to pass the initial password to the release task and for that we can use release variables:
CD Variables

deploy-initial.ps1 uses following parameters:

Name Example values
-ResourceGroupName yourservice-dev-rg
-VaultName yourservice-dev-kv
-Location "North Europe"
-AdminPassword (ConvertTo-SecureString -String "$(AdminPassword)" -Force -AsPlainText)
-ServicePrincipal "abcdefgh-abcd-abcd-abcd-abcdabcdabcd"

Above values are just examples and for the -ServicePrincipal you need to have your real SPN guid. That can be retrieved with following query:

And why is that required then? Well even in the SPN creates the Key Vault it doesn’t have access rights to it for the required operations. Therefore, we need to explicitly grant access rights to the Key Vault to our SPN and for that you need to have identifier.

Entire beef of the deploy-initial.ps1 is this (full source is available at GitHub):

After deploy-initial.ps1 has done it’s magic we can move to next step which is deploy.ps1. Now this script can already rely that Key Vault is up and running and configured correctly. So now we can use Key Vault to retrieve secrets and pass them on directly to the deployment part as parameters:

It’s also very nice way to pass any other parameters to your deployment if you need to. Example different SKUs for different environment, different scaling per environment etc.
Maybe needless to say but passing parameters for ARM deployments gives you that required flexibility and dynamic behavior which you definitely want.

Bonus part

And since we have now Key Vault secrets available in our CD process we can do whatever we want with them. To give you wild and crazy demo I’ll just show that you can upload some custom bash script to the server and run it as part of your CD process (you could even generate that script in your deployment script Smiley). This highlighted text is coming from bash script output:
CD Custom bash script
Check out the source here.

Closing words

In this blog post I wanted to demonstrate PowerShell + ARM combination and the capabilities it provides. With this you should be able to enhance your deployment logic beyond the plain ARM capabilities.

You can find the source code used in this example application from GitHub at JanneMattila / 200-UbuntuDocker.

Anyways... Happy hacking!

Comments (2)
  1. Matthew Short says:

    This line doesn’t work for us.
    $spn = (Get-AzureRmADServicePrincipal -SearchString “yourspnname”)[0]

    The reason being, the Service PRincipal that the deployment is running under, doesn’t have the permissions to query the AAD for its own object ID.

    {“odata.error”:{“code”:”Authentication_Unauthorized”,”message”:{“lang”:”en”,”value”:”Access denied to the specified API version.”}}}

    How have you got around this issue in your deployments?

    NB I should admit that we are using Octopus, not TFS to deploy.

  2. You should get that information offline using for example your own account. That is then just send using parameter to the release definition (parameter is passed in here https://github.com/JanneMattila/200-UbuntuDocker/blob/master/deploy/deploy-initial.ps1#L31).

    Also maybe this comment helps: https://github.com/JanneMattila/200-UbuntuDocker/blob/master/deploy/deploy-initial.ps1#L10-L14

Comments are closed.

Skip to main content