I’ve started working with Azure DevOps (previously known as Visual Studio Team Services – VSTS, previously known as Visual Studio Online…).
I’m using it to automated Azure deployments in some projects.
In our setup we have several different environments (dev, test, prod, etc.). Depending on the project, the environments are either Azure Resource Groups or completely different Azure subscriptions. In any case, we have created service principal accounts in our Azure Active Directory, that have the necessary Contributor roles to those subscriptions or resource groups, where they should be able to deploy resources.
The service principals have been added to Azure DevOps as Service Connections, so that we have a service connection for each project and environment.
We want to use a Release Pipeline for each project. Each Pipeline has a stage per environment.
Releases are triggered using package management (in these projects we don’t use VSTS for source control and build). Instead, our build server generates NuGet packages and uploads them to Azure DevOps package management, and this triggers the release process.
The Pipeline looks like this:
This works fine, but there are some limitations in how Azure DevOps works with release stages, that makes it somewhat unhandy to work with:
If the release steps need changing, we need to perform the same modification in each environment (called a Stage in Azure DevOps). There is no easy way to make a change to one stage and have it apply to all stages.
Instead, we make the changes only in the Dev stage. Then we delete the Test and Production stages, and make clones of the Dev stage. Since we use parameters for everything, we only have to change a few things after cloning the Dev stage.
This works well for us, except one thing. A lot of the pipeline tasks are related to the Service Connection i.e. Azure Subscription, which differs for each deployment environment. And it is not possible to use a variable for the service connection (unless you put them all in Task Groups as a workaround).
Since we have quite a lot of steps, we don’t want to edit the Azure Subscription (which is just another name for the Service Connection) on all of them. Even if we put tasks in groups using the above workaround, that will still give us several places to modify.
Instead, we use a Pipeline Process Parameter to select the Azure Subscription once for each deployment stage. Process Parameters are like global variables for a stage in Azure DevOps.
If you have created a new release pipeline based in the App Service Deployment template, then you have already seen a subscription Process Parameter. A pipeline based on that template looks like this:
There is a Parameters heading at the Stage level in the Stage Tasks page.
If you create a new pipeline based on a template that doesn’t have process parameters, the Stage Tasks page only contains the Stage name. It is not possible to add parameters through the Azure DevOps web page.
Edit the Pipeline JSON
If you want to add a process parameter to an existing pipeline, or a new pipeline based on a template that doesn’t have it, you need to add it by editing the pipeline JSON file.
To get the JSON file, you need to find the Release Pipeline that you want to edit, click the … next to it and select Export.
This will download the JSON file that describes the entire pipeline.
For the rest of this post, I’ve used a pipeline that was creating using an empty job template, and only added a single Azure Resource Group deployment task.
Looking at the JSON file, it starts with a lot of metadata about the pipeline. It gets interesting at the “environments” property, which defines the stages. Below that is a property called deployPhases
which describes the different agent jobs in that stage. In the almost empty template I’m working with here, there is only one deployment phase whose name
property value is “Agent job”.
A deployment phase has an array of workflowTasks
which contains the steps/tasks for that agent job. In the template here, there is one workflow task called “Azure Deployment:Create Or Update Resource Group action”.
It has an inputs
property, which contains the properties that can be defined in the Task configuration UI.
One of these is called ConnectedServiceName
. That is a GUID which references the Service Connection that was selected in the UI.
This is the property that we want to have on a per-stage level, and not on each task.
Further down the file is a property called processParameters
. For this template, it is empty. If the pipeline had been created using the Azure App Service deployment template, it would already contain a ConnectedServiceName process parameter.
The processParameters
property can contain a sub property called inputs
which is an array of process parameters that can be configured on a per agent job level.
By adding the inputs
property with a single property, so the entire processParameters
property looks like this, you can add a ConnectedServiceName parameter which is an Azure Resource Manager service connection:
"processParameters": { "inputs": [ { "aliases": [], "options": {}, "properties": {}, "name": "ConnectedServiceName", "label": "Azure subscription", "defaultValue": "0aee1cb6-fbc9-267b-b391-1e69c1d60f63f, "required": true, "type": "connectedService:AzureRM", "helpMarkDown": "Select the Azure Resource Manager subscription for the deployment.", "visibleRule": "", "groupName": "" } ] }
You need to have a valid defaultValue
value. You can use the same GUID as in the workflow step’s ConnectedServiceName
property mentioned above.
After adding the new ConnectedServiceName
process parameter, you can reference it in other places. Change the above mentioned ConnectedServiceName
property to be $(Parameters.ConnectedServiceName)
to make the link between the two.
Then save the file, and upload to Azure DevOps by importing it as a new release pipeline.
In the newly imported release pipeline you can select the Azure Subscription once per stage, and then you can reuse it in all tasks in that stage.
When you want to reference the subscription in another task, enter the variable name $(Parameters.ConnectedServiceName)
in the field where you want to use it.
TIP: There seems to be a bug, where if you enter it normally, the UI thinks the value is not correctly entered. This means you can’t save the pipeline definition.The workaround is to write the “$()
” first, and then fill in the variable name afterwards.
The moment the Azure DevOps task editing UI detects that you have entered a process parameter name, the field turns read-only, and the value is now linked to the process parameter. If you run into the bug mentioned before, you can’t fix it in any other way than to recreate the current task.
Other tips
You can have multiple connected services. If your pipeline needs to access different connected services/Azure Subscriptions, you can add multiple process parameters like the one above. They just need to have different names. The variable name will be Parameters.YourProcessParameterName
instead of Parameters.ConnectedServiceName
as in my example.