Search code examples
azureazure-devopsazure-pipelinesazure-pipelines-yaml

Is it possible to cancel a Azure DevOps pipeline Job programmatically?


As it is possible to stop a single step in a Azure DevOps pipeline:

echo "##vso[task.complete result=Succeeded;]DONE"

See: https://github.com/microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md#task-logging-commands

Is it also possible to check a condition and stop the whole pipeline run or job depending on that?

PS. I know, you can set conditions to jobs, but in my case the whole pipeline is a single job and it does not make sense to split it into multiple jobs, because of other reasons.


Solution

  • You can cancel a build through REST API:

    PATCH https://dev.azure.com/atbagga/atbagga/_apis/build/Builds/120
    Request content: {'status': 'Cancelling'}
    

    Here you have an example:

    steps:
    - task: PowerShell@2
      name: ConditionalStep
      inputs:
        targetType: 'inline'
        script: |
          Write-Host "I'm here"
          Write-Host ('$(SomeVariable)' -eq 'Stop')
          if ('$(SomeVariable)' -eq 'Stop') {
            $uri = "https://dev.azure.com/thecodemanual/DevOps Manual/_apis/build/builds/$(Build.BuildId)?api-version=5.1"
    
            $json = @{status="Cancelling"} | ConvertTo-Json -Compress
    
            $build = Invoke-RestMethod -Uri $uri -Method Patch -Headers @{Authorization = "Bearer $(System.AccessToken)"} -ContentType "application/json" -Body $json
    
            Write-Host $build
          }
          Write-Host "And now here!"
        pwsh: true
    - pwsh: Start-Sleep -Seconds 60    
    - task: PowerShell@2
      inputs:
        targetType: 'inline'
        script: |
          $uri = "https://dev.azure.com/thecodemanual/DevOps Manual/_apis/build/builds/$(Build.BuildId)/timeline?api-version=5.1"
    
          Write-Host $uri
    
          # Invoke the REST call
          $build = Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization = "Bearer $(System.AccessToken)"} -ContentType "application/json"
    
          $taskResult = $build.records | Where-Object {$_.name -eq "ConditionalStep" } | Select-Object result
    
          Write-Host $taskResult.result
    
        pwsh: true
    

    For that you will get that output:

    enter image description here

    If you get such error:

     | {"$id":"1","innerException":null,"message":"Access denied.
     | DevOps Manual Build Service (thecodemanual) needs Stop builds
     | permissions for vstfs:///Build/Build/1611 in team project
     | DevOps Manual to perform the
     | action.","typeName":"Microsoft.TeamFoundation.Build.WebApi.AccessDeniedException, Microsoft.TeamFoundation.Build2.WebApi","typeKey":"AccessDeniedException","errorCode":0,"eventId":3000}
    

    Please make sure that your build account has permission to stop a build:

    You will find this under this section:

    enter image description here

    enter image description here

    Please note

    What you can't do is set a build as completed. If you dod this. Whole pipeline will be still executed. So if this isn't what you want, you need to add condition to every step with an output variable set previously in the pipeline and in that way ignore those steps.

    steps:
    - task: PowerShell@2
      name: ConditionalStep
      inputs:
        targetType: 'inline'
        script: |
          Write-Host "I'm here"
          Write-Host ('$(SomeVariable)' -eq 'Stop')
          if ('$(SomeVariable)' -eq 'Stop') {
            Write-Host '##vso[task.setvariable variable=shouldStop;isOutput=true]Yes'
          }
          Write-Host "And now here!"
        pwsh: true
    - pwsh: Start-Sleep -Seconds 60
      condition: ne(variables['ConditionalStep.shouldStop'], 'Yes')
    - task: PowerShell@2
      condition: ne(variables['ConditionalStep.shouldStop'], 'Yes')
      inputs:
        targetType: 'inline'
        script: |
          $uri = "https://dev.azure.com/thecodemanual/DevOps Manual/_apis/build/builds/$(Build.BuildId)/timeline?api-version=5.1"
    
          Write-Host $uri
    
          # Invoke the REST call
          $build = Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization = "Bearer $(System.AccessToken)"} -ContentType "application/json"
    
          $taskResult = $build.records | Where-Object {$_.name -eq "ConditionalStep" } | Select-Object result
    
          Write-Host $taskResult.result
    
        pwsh: true