Ragmar
Ragmar

Reputation: 350

Azure pipeline job succeeded with certain task succeed

I have following job structure:

Main goal is to run Publish artifact if any task succeed, if all of them fail, Publish artifact should not be executed and the job should fail.

Things I try and consider:

Solutions I have tried:

I try to use a custom condition on Publish artifact asking the result of all Tasks before, but azure pipeline doesn't hold result stauts for tasks.

I also try to use continueOnError on every task except the last task (Task N) but if this taks fail the whole process fails even if one of them succeeded.

I tried this solution but the script in this solution doesn't know if the task I'm setting the variable for actually passed or not, it will just consider that any task failed and it will not be sure which one is the one that fail.

Any recommendation on how I can make this pass?

Upvotes: 0

Views: 7587

Answers (3)

Ragmar
Ragmar

Reputation: 350

There are 2 possible answers each with some problems on their own. But they do resolve the issue.

Making every task a job

This will require that every task is treated as a individual job, so the condition and dependency is on jobs and should be pass between them.


    # Sample pipeline
    # Avoiding setup and rest of variables

    - job: task_1
      steps:
 
        - task: Bash@3
          name: task1
          displayName: Task 1
          continueOnError: true
          inputs:
            targetType: 'inline'
            script: |
              echo 'Task 1'

        - task: Bash@1
            displayName: 'Publish Artifact'
            inputs:
            targetType: 'inline'
            script: |
              echo "Publish artifact"


    - job: task_2
      dependsOn: 
        - task_1
      condition: eq(dependencies.task_1.result,'SucceededWithIssues')
      steps:
      
        - task: Bash@3
          name: task2
          displayName: Task 2
          continueOnError: true
          inputs:
            targetType: 'inline'
            script: |
              echo 'Task 2'

        - task: Bash@1
            displayName: 'Publish Artifact'
            inputs:
            targetType: 'inline'
            script: |
              echo "Publish artifact"


    #Keep on doing tasks...

    - job: task_N
      dependsOn: 
        - task_N-1
      condition: eq(dependencies.task_N-1.result,'SucceededWithIssues')
      steps:
    
        - task: Bash@3
          name: task_N
          displayName: Task N
          inputs:
            targetType: 'inline'
            script: |
              echo 'Task N'

        - task: Bash@1
            displayName: 'Publish Artifact'
            inputs:
            targetType: 'inline'
            script: |
              echo "Publish artifact"

Note:

  • It is necesary than the last task (Task N in the example) does not have the ContinueOnError flag, so you know that all the jobs failed if the last one is not executed.
  • First task does not have a dependency

Pros:

  • We avoid to call tasks if one of them succeeded, avoiding to execute code that will not be needed

Cons:

  • This will run the publish artifact task many times as the ContinueOnError is a success and the publish will continue to happen.
  • If another job need to be done after the rest of the task, it will depend on every job and the condition will have to include the SuccessWithIssues and it will be annoying writing each of the jobs in there.
  • If the task require to get some data or information, it is necessary to pass it to each job, or downloading any artifacts necessary on each one. An additional step which is repeated on every job
  • Pipeline will return as warning if a job SucceededOrFailed

Using scripting to print variables

I did not manage to test this example on the code that I needed, but looks logical and I don't see why it should failed. This was based on @Vito Liu-MSFT answer

    # Sample pipeline

    - job: task_1
      steps:
        - task: Bash@3
          name: task1
          displayName: Task 1
          inputs:
            targetType: 'inline'
            script: |
              echo 'Task 1'
          failOnStderr: true

        - task: Bash@3
          name: task2
          displayName: Task 2
          condition: failed()
          inputs:
            targetType: 'inline'
            script: |
              echo 'Task 1'
          failOnStderr: true

        # Keep doing tasks
        - task: Bash@3
          name: taskN
          displayName: Task N
          inputs:
            targetType: 'inline'
            script: |
              echo 'Task 1'
          failOnStderr: true
          condition: failed()

         - task: PowerShell@2
           inputs:
             targetType: 'inline'
            script: |
              $PAT="{pat}"
              $base64AuthInfo= System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($PAT)"))
    
              #List all build timeline via build ID
              $ListAllBuildTimeLineURL="https://dev.azure.com/{Org name}/{Project name}/_apis/build/builds/$(Build.BuildId)/timeline?api-version=6.1-preview.2"
              $ListAllBuildTimeLineResult = Invoke-RestMethod -Uri $ListAllBuildTimeLineURL -Headers @{Authorization = "Basic {0}" -f $base64AuthInfo} -Method get
              $ListAllBuildTimeLineResult.records.Count
      
              #Check task result and set variable
              foreach($Task in $ListAllBuildTimeLineResult.records){
                if($Task.name -eq "task1"){
                  if($Task.result -eq "succeeded"){
                      Write-Host "##vso[task.setvariable variable=task1.status]Success"
                  }
                }
                if($Task.name -eq "task2"){
                  if($Task.result -eq "succeeded"){
                      Write-Host "##vso[task.setvariable variable=task2.status]Success"
                  }
                }
                # Keep printing each task if succeed

                if($Task.name -eq "taskN"){
                  if($Task.result -eq "succeeded"){
                      Write-Host "##vso[task.setvariable variable=taskN.status]Success"
                  }
                }
              }
          condition: SucceededOrFailed()

        - task: Bash@1
          displayName: 'Publish Artifact'
          inputs:
            targetType: 'inline'
            script: |
              echo "Publish artifact"
           condition: |
             or(
               SucceededOrFailed(),
               or(
                 eq(variables['task1.status'], 'success'),
                 or(
                   eq(variables['task2.status'], 'success'),
                   or(
                     # Keep writing ors
                     eq(variables['taskN-1.status'], 'success')
                     eq(variables['taskN.status'], 'success')
                   )
                 )
               )
             )

Notes:

  • Script for printing variables must have all the task included there. It should be executed as SucceededOrFailed
  • First task does not have a condition when running.

Pros:

  • We need to download dependencies just for one job, all things needed does not need to be sending to each individual task
  • We don't have to repeat the publish artifact task every time.

Cons:

  • All the task are executed if the first one fail.
  • A script to write variables is necessary to write the status of each task.
  • An URL is needed to access the status of the build and the name of the task. If URL change, pipeline will need to change.
  • And extended condition Publish Artifact need to happen to know that everything worked fine.
  • Future task in the job will need the SucceededOrFail to keep running.

I did not test this and I am unsure, but as the task failed I believe the Status of the Job will be failed, future dependencies will have to handle this case, which will be uncertain if the job actually failed or not. Pipeline will probably return as error. A better result could be achieved when changing failed with ContinueOnError, and the job will return SucceededWithIssues.

Upvotes: 4

Vito Liu
Vito Liu

Reputation: 8278

  1. If Task N fails, the Publish task should not run and the job should fail.
  2. If Task N success and another task success, the Publish task should run.

Check this YAML, we could add a power shell task and call the REST API to check the task result.


# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml

trigger: none

pool:
  vmImage: ubuntu-latest

steps:
- task: PowerShell@2
  displayName: task1
  inputs:
    targetType: 'inline'
    script: 'Write-Host "Hello World"'

- task: PowerShell@2
  displayName: task2
  inputs:
    targetType: 'inline'
    script: 'Write-Host "Hello World" $(xasda)'

- task: PowerShell@2
  displayName: taskn
  inputs:
    targetType: 'inline'
    script: 'Write-Host "Hello World"' 
  condition: always()


- task: PowerShell@2
  inputs:
    targetType: 'inline'
    script: |
      $PAT="{pat}"
      $base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($PAT)"))
      
      #List all build timeline via build ID
      $ListAllBuildTimeLineURL="https://dev.azure.com/{Org name}/{Project name}/_apis/build/builds/$(Build.BuildId)/timeline?api-version=6.1-preview.2"
      $ListAllBuildTimeLineResult = Invoke-RestMethod -Uri $ListAllBuildTimeLineURL -Headers @{Authorization = "Basic {0}" -f $base64AuthInfo} -Method get
      $ListAllBuildTimeLineResult.records.Count
      
      #Check task result and set variable
      foreach($Task in $ListAllBuildTimeLineResult.records){
              if($Task.name -eq "taskn"){
                  #write-host $Task.state
                  #write-host $Task.id
                  if($Task.result -eq "succeeded"){
                      Write-Host "##vso[task.setvariable variable=taskn.status]Success"
                  }
              }
              if($Task.name -eq "task1"){
                  if($Task.result -eq "succeeded"){
                      Write-Host "##vso[task.setvariable variable=task.status]Success"
                  }
              }
              if($Task.name -eq "task2"){
                  if($Task.result -eq "succeeded"){
                      Write-Host "##vso[task.setvariable variable=task.status]Success"
                  }
              }
      }
  condition: always()

#
- task: Bash@3
  displayName: publishArtifact
  inputs:
    targetType: 'inline'
    script: 'printenv'
  condition: and(eq(variables['taskn.status'], 'success'),eq(variables['task.status'], 'success'))

Result:

Note: The Task Bash should be the task Publish artifact

enter image description here

Upvotes: 1

Killian
Killian

Reputation: 424

I'm not sure if I follow you exactly but I think this might do what you need.

Assuming I am correct that:

  1. A task should run only if the preceding task(s) failed.
  2. The Publish task should run if any of the preceding tasks succeeded
  3. If Task N fails, the Publish task should not run and the job should fail

# Sample pipeline
# Inline bash scripts to simulate failing tasks.
# Comment out the 'exit 1' line to allow a task to succeed
# The Publish Artifact task below is just a fake for the purposes of testing

steps:
  - task: Bash@3
    name: task1
    displayName: Task 1
    continueOnError: true
    inputs:
      targetType: 'inline'
      script: |
        echo 'Task 1'
        exit 1
        echo "##vso[task.setvariable variable=task1_succeeded]true"
      failOnStderr: true
      
  - task: Bash@3
    name: task2
    displayName: Task 2
    condition: ne(variables.task1_succeeded, true)
    continueOnError: true
    inputs:
      targetType: 'inline'
      script: |
        echo 'Task 2'
        exit 1
        echo "##vso[task.setvariable variable=task2_succeeded]true"
      failOnStderr: true
      
  - task: Bash@3
    name: task3
    displayName: Task 3
    condition: |
      and(
        ne(variables.task1_succeeded, true),
        ne(variables.task2_succeeded, true)
      )
    continueOnError: true
    inputs:
      targetType: 'inline'
      script: |
        echo 'Task 3'
        exit 1
        echo "##vso[task.setvariable variable=task3_succeeded]true"
      failOnStderr: true

  - task: Bash@3
    name: taskN
    displayName: Task N
    condition: |
      and(
        ne(variables.task1_succeeded, true),
        ne(variables.task2_succeeded, true),
        ne(variables.task3_succeeded, true)
      )
    continueOnError: false
    inputs:
      targetType: 'inline'
      script: |
        echo 'Task N'
        exit 1
        echo "##vso[task.setvariable variable=taskN_succeeded]true"
      failOnStderr: true

  - task: Bash@3
    name: publishArtifact
    condition: |
      or(
        eq(variables.task1_succeeded, true),
        eq(variables.task2_succeeded, true),
        eq(variables.task3_succeeded, true),
        eq(variables.taskN_succeeded, true)
      )
    displayName: 'Publish Artifact'
    inputs:
      targetType: 'inline'
      script: |
        echo 'Pseudo Publish Artifact'
      failOnStderr: true

Upvotes: 1

Related Questions