Task Control Option - Custom Condition - run task when previous failed or timed out

Is there an option to set the custom condition that will test if the previous task has failed OR timed out?

Currently, I'm using the Only when a previous task has failed which works when the task fails. If the task times out, then it is not considered an error and it is skipped.

I need a custom condition then, something like or(failed(), timedout()). Is it possible?

Context

We have this intermittent problem with the npm install task that we can't find a reason for but it is resolved with next job run, so we were searching for a retry functionality. Partial solution was to duplicate npm install and use the Control Option but it wasnt working for all "failure" cases. Solution gave by @Levi Lu-MSFT seems to be working for all our needs (it does retry) but sadly it doesnt solve the problem, 2nd line repeated task also fails.

Sample errors:

20741 error   stack: 'Error: EPERM: operation not permitted, unlink \'C:\\agent2\\_work\\4\\s\\node_modules\\.staging\\typescript-4440ace9\\lib\\tsc.js\'',
20741 error   errno: -4048,
20741 error   code: 'EPERM',
20741 error   syscall: 'unlink',
20741 error   path: 'C:\\agent2\\_work\\4\\s\\node_modules\\.staging\\typescript-4440ace9\\lib\\tsc.js',
20741 error   parent: 's' }
20742 error The operation was rejected by your operating system.
20742 error It's possible that the file was already in use (by a text editor or antivirus),
20742 error or that you lack permissions to access it.

or

21518 verbose stack SyntaxError: Unexpected end of JSON input while parsing near '...ter/doc/TypeScript%20'
21518 verbose stack     at JSON.parse (<anonymous>)
21518 verbose stack     at parseJson (C:\agent2\_work\_tool\node\8.17.0\x64\node_modules\npm\node_modules\json-parse-better-errors\index.js:7:17)
21518 verbose stack     at consumeBody.call.then.buffer (C:\agent2\_work\_tool\node\8.17.0\x64\node_modules\npm\node_modules\node-fetch-npm\src\body.js:96:50)
21518 verbose stack     at <anonymous>
21518 verbose stack     at process._tickCallback (internal/process/next_tick.js:189:7)
21519 verbose cwd C:\agent2\_work\7\s
21520 verbose Windows_NT 10.0.14393
21521 verbose argv "C:\\agent2\\_work\\_tool\\node\\8.17.0\\x64\\node.exe" "C:\\agent2\\_work\\_tool\\node\\8.17.0\\x64\\node_modules\\npm\\bin\\npm-cli.js" "install"
21522 verbose node v8.17.0
21523 verbose npm  v6.13.4
21524 error Unexpected end of JSON input while parsing near '...ter/doc/TypeScript%20'
21525 verbose exit [ 1, true ]

Sometimes also time's out

Upvotes: 2

Views: 6097

Answers (2)

Levi Lu-MSFT
Levi Lu-MSFT

Reputation: 30333

It is possible to add a custom condition. If you want the task to be executed when previous task failed or skipped, you can use custom condition not(succeeded())

enter image description here

However there is a problem with above custom condition, it does not work in the multiple tasks scenario.

For example, there are three tasks A,B,C. The expected behavior is Task C gets executed only when Task B failed. But the actual behavior is Task C will also get executed when Task A failed even if Task B succeeded. Check below screenshot.

enter image description here

The workaround for above problem is to add a script task to call azure devops restful api to get the status of Task B and set it to a variable using this expression echo "##vso[task.setvariable variable=taskStatus]taskStatus".

For below example, Add a powershell task (You need to set conditon for this task to Even if a previous task has failed, even if the build was canceled to always run this powershell task) before Task C to run below inline scripts:

$url = "$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/timeline?api-version=5.1"

$result = Invoke-RestMethod -Uri $url -Headers @{authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"} -ContentType "application/json" -Method get

#Get the task B's task result  
$taskResult = $result.records | where {$_.name -eq "B"} | select result  

#set the Task B's taskResult to variable taskStatus
echo "##vso[task.setvariable variable=taskStatus]$($taskResult.result)" 

In order above scripts can access the access token, you also need to click the Agent job and check option Allow scripts to access the OAuth token. Refer to below screenshot.

enter image description here

At last you can use custom condition and(not(canceled()), ne(variables.taskStatus, 'succeeded')) for Task C. Task C should be executed only when Task B not succeeded.

enter image description here

Upvotes: 5

Yan Sklyarenko
Yan Sklyarenko

Reputation: 32250

Although I failed to find a built-in function to detect if a build step is timed out, you can try to emulate this with the help of variables.

Consider the following YAML piece of pipeline declaration:

steps:
- script: |
    echo Hello from the first task!
    sleep 90
    echo "##vso[task.setvariable variable=timedOut]false"
  timeoutInMinutes: 1
  displayName: 'A'
  continueOnError: true

- script: echo Previous task has failed or timed out!
  displayName: 'B'
  condition: or(failed(), ne(variables.timedOut, 'false'))

The first task (A) is set to time out after 1 minute, but the script inside emulates the long-running task (sleep 90) for 1.5 minutes. As a result, the task times out and the timedOut variable is NOT set to false. Hence, the condition of the task B evaluates to true and it executes. The same happens if you replace sleep 90 with exit 1 to emulate the task A failure.

On the other hand, if task A succeeds, neither of the condition parts of task B evaluates to true, and the whole task B is skipped.

This is a very simplified example, but it demonstrates the idea which you can tweak further to satisfy the needs of your pipeline.

Upvotes: 2

Related Questions