Azure Pipelines: Bulk approve of deployments to environments

Is there any way to approve runs via the CLI or the API (or anything else)? I'm looking for a way to bulk approve multiple runs from different pipelines but it's not available in the UI.

Let's say I have 100 pipelines that have a deployment job to a production environment. I would like to approve all awaiting for approval runs.

Currently, I cannot find something like it in the docs of the Azure DevOps REST API or the CLI.

The feature docs: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/environments https://docs.microsoft.com/en-us/azure/devops/pipelines/process/approvals

The following question is related but I'm looking for any way of solving it but not just via API: Approve a yaml pipeline deployment in Azure DevOps using REST api


Solution 1:

I was just searching for an answer for this regarding getting the approval id that you would need. In fact there is an undocumented API to approve an approval check.

This is as Merlin explain the following

https://dev.azure.com/{org}/{project}/_apis/pipelines/approvals/{approvalId}

The body has to look like this

[{
    "approvalId": "{approvalId}",
    "status": {approvalStatus},
    "comment": ""
}]

where {approvalStatus} is telling the API if you approved or not. You probly have to try, but I had a 4 as a status. I guess there are only 2 possibilities. Either for "approved" or "denied".

The question is now how you get the approval ID? I found it. You get it by using the timeline API of a classic build. The build API documentation says that you get it by the following

https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}?api-version=5.1

the build timeline you get in the response of the build run, but it has a pattern which is

https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}/Timeline?api-version=5.1

Besides a flat array container a parent / child rleationship from stage, phase, job and tasks, you can find within it something like the following:

{
  "records": [
    {
      "previousAttempts": [
        
      ],
      "id": "95f5837e-769d-5a92-9ecb-0e7edb3ac322",
      "parentId": "9e7965a8-d99d-5b8f-b47b-3ee7c58a5b1c",
      "type": "Checkpoint",
      "name": "Checkpoint",
      "startTime": "2020-08-14T13:44:03.05Z",
      "finishTime": null,
      "currentOperation": null,
      "percentComplete": null,
      "state": "inProgress",
      "result": null,
      "resultCode": null,
      "changeId": 73,
      "lastModified": "0001-01-01T00:00:00",
      "workerName": null,
      "details": null,
      "errorCount": 0,
      "warningCount": 0,
      "url": null,
      "log": null,
      "task": null,
      "attempt": 1,
      "identifier": "Checkpoint"
    },
    {
      "previousAttempts": [
        
      ],
      "id": "9e7965a8-d99d-5b8f-b47b-3ee7c58a5b1c",
      "parentId": null,
      "type": "Stage",
      "name": "Power Platform Test (orgf92be262)",
      "startTime": null,
      "finishTime": null,
      "currentOperation": null,
      "percentComplete": null,
      "state": "pending",
      "result": null,
      "resultCode": null,
      "changeId": 1,
      "lastModified": "0001-01-01T00:00:00",
      "workerName": null,
      "order": 2,
      "details": null,
      "errorCount": 0,
      "warningCount": 0,
      "url": null,
      "log": null,
      "task": null,
      "attempt": 1,
      "identifier": "Import_Test"
    },
    {
      "previousAttempts": [
        
      ],
      "id": "e54149c5-b5a7-4b82-8468-56ad493224b5",
      "parentId": "95f5837e-769d-5a92-9ecb-0e7edb3ac322",
      "type": "Checkpoint.Approval",
      "name": "Checkpoint.Approval",
      "startTime": "2020-08-14T13:44:03.02Z",
      "finishTime": null,
      "currentOperation": null,
      "percentComplete": null,
      "state": "inProgress",
      "result": null,
      "resultCode": null,
      "changeId": 72,
      "lastModified": "0001-01-01T00:00:00",
      "workerName": null,
      "details": null,
      "errorCount": 0,
      "warningCount": 0,
      "url": null,
      "log": null,
      "task": null,
      "attempt": 1,
      "identifier": "e54149c5-b5a7-4b82-8468-56ad493224b5"
    }
  ],
  "lastChangedBy": "00000002-0000-8888-8000-000000000000",
  "lastChangedOn": "2020-08-14T13:44:03.057Z",
  "id": "86fb4204-9c5e-4e72-bdb1-eefe230480ec",
  "changeId": 73,
  "url": "https://dev.azure.com/***"
}

below you can see a step that is called "Checkpoint.Approval". The id of that step IS the approval Id you need to approve everything. If you want to know from which stage the approval is, then you can follow up the parentIds until the parentId property is null. This will then be the stage.

With this you can successfully get the approval id and use it to approve with the said

Solution 2:

What jessehouwing's guess is correct. Now multi-stage still be in preview, and the corresponding SDK/API/extension hasn't been expanded and provided to public.

You may think that what about not using API. I have checked the corresponding code from our backend, all of operations to multi-stage approval contain one required parameter: approvalId. I'm sure you have known that this value is unique and different approval map with different approvalId value. This means, no matter which method you want to try with, approvalId is the big trouble. And based on my known, until now, there's no any api/SDK, third tool or extension can achieve this value directly.


In addition, for multi-stage YAML, its release process logic is not same with the release that defined with UI. So, all of public APIs which can work with release(UI), are not suitable with the release of multi-stage.

We have one undisclosed api, can get Approval message of multi-stage:

https://dev.azure.com/{org}/{project}/_apis/pipelines/approvals/{approvalId}

You can try with listing approval without specifying approvalId: https://dev.azure.com/{org}/{project}/_apis/pipelines/approvals. And its response message: Query for approvals failed. A minimum of one query parameter is required.\r\nParameter name: queryParameters. This represents you must tell system the specified approval(the big trouble I mentioned previously).

In fact, for why approvalId is a necessary part, it is caused from our backend code structure. I'd suggest you raise suggestion on developing API/SDK for multi-stage here.

Solution 3:

I can confirm that Sebastian's answer worked for me, even in Azure DevOps 2020 on-prem. After retrieving the approvalId from either methods used above (I was specifically using a service hook for my integration), I used the following API PATCH call:

https://dev.azure.com/{organization}/{project}/_apis/pipelines/approvals/?api-version=6.0-preview

and in the body:

[
    {
        "approvalId": "{approvalId}",
        "status": {status integer}, (4 - approved; 8 - rejected)
        "comment": ""
    }
]

The call is passed with the application/json Content-Type, but in some situations it did not like that I was using the [] brackets, so you will need to work around that, only then will the call work. I was even able to integrate this call into my custom connector in MS Power Automate

Solution 4:

The az pipelines extension doesn't suport approvals yet, I suppose due to the fact that multi-stage pipelines are still in preview and the old release hub will eventually be replaced by it.

But there is a REST API you can use to list and update approvals. These can be called from PowerShell with relative ease.

Or use the vsteam powershell module and Get-VSTeamApproval and Set-VSTeamApproval.