18

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.

Krzysztof Madej
  • 32,704
  • 10
  • 78
  • 107
Matthias
  • 1,055
  • 2
  • 14
  • 26
  • Agree with Krzysztof Madej. This is some additional information. 1. If you use the Rest Api to cancel the build, the pipeline state will be marked as "canceled". 2. Since you don't want to add code, you could add condition to every tasks. In this case, the pipleine will run and be marked as "Success" , but all the tasks will be skipped. You could refer to this [ticket](https://stackoverflow.com/questions/55891350/azure-pipelines-how-to-break-cancel-a-release-based-on-a-conditional-task-re). – Kevin Lu-MSFT May 28 '20 at 08:50
  • @KevinLu-MSFT. I knew this kind of solution before. That does solve the problem somehow, but I see there is not elegant way to solve this yet. – Matthias Jun 02 '20 at 09:47

2 Answers2

35

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
Krzysztof Madej
  • 32,704
  • 10
  • 78
  • 107
1

There is a different way to cancel the pipeline using the python script and the REST API inside the pipeline.

       - task: PythonScript@0
         inputs:
            scriptSource: 'inline'
            script: |
              import requests
              import json
              import base64

              pat = 'your personal token access'
              authorization = str(base64.b64encode(bytes(':'+pat, 'ascii')), 'ascii')

              # Returns a JSON - serializable representation of the pipeline.
              def get_builds():
                  url_pipeline = "https://dev.azure.com/{Organisation}/{Project}/_apis/pipelines/{definitionId}/runs?api-version=6.0-preview.1"
                  headers = {
                      'Authorization': 'Basic '+authorization,
                      'Content-Type': 'application/json',
                  }
                  response = requests.request("GET", url_pipeline, headers=headers)
                  json_data = response.json()
                  with open("data.json", "w") as outfile:
                      json.dump(json_data, outfile)
              get_builds()

              # Get buildId from data. json file
              def get_buildId():
                  with open('data.json') as json_file:
                      data = json.load(json_file)
                      buildId = data['value'][0]['id']
                      print(buildId)
                      return buildId
              get_buildId()

              # Cancel a build pipeline by buildId and pipelineId.
              def cancel_build():
                url = "https://dev.azure.com/{Organisation}/{Project}/_apis/build/builds/"+str(get_buildId())+"?api-version=6.0"
                payload = json.dumps({
                    "status": "Cancelling"
                })
                headers = {
                    'Authorization': 'Basic '+authorization,
                    'Content-Type': 'application/json',
                }
                requests.request("PATCH", url, headers=headers, data=payload)
              cancel_build()

you have to add another task after task pythonScript for install libraries requests

       - task: AzureCLI@2
         displayName: 'install required libraries'
         inputs:
           azureSubscription: '$(AZURE_RM_SVC_CONNECTION)'
           scriptType: ps
           scriptLocation: inlineScript
           inlineScript: |
             pip install requests