Azure 管道作业成功完成某些任务

Azure pipeline job succeeded with certain task succeed

我有以下工作结构:

主要目标是 运行 发布工件 如果任何任务成功,如果所有任务都失败,发布工件 应该不会被执行,作业应该会失败。

我尝试和考虑的事情:

我尝试过的解决方案:

我尝试在 Publish artifact 上使用自定义条件询问之前所有任务的结果,但 azure pipeline 不保存任务的结果状态。

我也尝试在每个任务上使用 continueOnError 除了最后一个任务(任务 N)但是如果这个任务失败整个过程即使其中之一成功,也会失败。

但是这个解决方案中的脚本不知道我设置变量的任务是否真的通过了,它只会认为任何任务失败了并且它不会确定哪一个失败了。

关于我如何通过此考试有什么建议吗?

我不确定我是否完全理解你的意思,但我认为这可能会满足你的需要。

假设我是正确的:

  1. 仅当前面的任务失败时才应 运行 执行任务。
  2. 如果前面的任何任务成功
  3. ,发布任务应该运行
  4. 如果任务 N 失败,则发布任务不应运行并且作业应该失败

# 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
  1. 如果任务 N 失败,发布任务不应 运行 并且作业应该失败。
  2. 如果任务 N 成功且另一个任务成功,则发布任务应该 运行。

检查这个 YAML,我们可以添加一个 power shell 任务并调用 REST API 来检查任务结果。


# 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'))

结果:

注意:任务Bash应该是任务Publish artifact

有 2 个可能的答案,每个答案都有自己的问题。但他们确实解决了这个问题。

让每项任务成为工作

这将要求将每个任务都视为一个单独的作业,因此条件和依赖性取决于作业,并且应该在它们之间传递。


    # 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"

注:

  • 它比最后一个任务(任务 N 在示例中)没有 ContinueOnError 标志是必要的,所以你知道如果最后一个作业没有执行,则所有作业都失败。
  • 第一个任务没有依赖关系

优点:

  • 我们避免在其中一个任务成功时调用任务,避免执行不需要的代码

缺点:

  • 这将 运行 多次发布工件任务,因为 ContinueOnError 成功并且发布将继续发生。
  • 如果在任务的其余部分之后需要完成另一项工作,这将取决于每项工作,并且条件必须包括 SuccessWithIssues 并且会很烦人那里的每个职位。
  • 如果任务需要获取一些数据或信息,则有必要将其传递给每个作业,或下载每个作业所需的任何工件。在每个作业上重复的附加步骤
  • 如果作业 SucceededOrFailed
  • ,管道将 return 作为警告

使用脚本打印变量

我没能在我需要的代码上测试这个例子,但看起来合乎逻辑,我不明白为什么它会失败。这是基于@Vito Liu-MSFT 的回答

    # 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')
                   )
                 )
               )
             )

备注:

  • 用于打印变量的脚本必须包含所有任务。它应该被执行为 SucceededOrFailed
  • 第一个任务在运行ning时没有条件。

优点:

  • 我们只需要为一项工作下载依赖项,不需要将所有需要的东西都发送到每个单独的任务
  • 我们不必每次都重复发布工件任务。

缺点:

  • 如果第一个任务失败,则执行所有任务。
  • 需要一个写变量的脚本来写每个任务的状态。
  • 需要 URL 才能访问构建状态和任务名称。如果 URL 改变,管道将需要改变。
  • 并且扩展条件发布工件需要碰巧知道一切正常。
  • 作业中的未来任务将需要 SucceededOrFail 来保持 运行ning。

我没有测试这个,我不确定,但由于任务失败,我相信作业的状态将失败,未来的依赖项将不得不处理这种情况,这将不确定作业是否真的失败或不是。管道可能会 return 作为错误。当更改因 ContinueOnError 而失败时,可以获得更好的结果,作业将 return SucceededWithIssues.