使用 PowerShell 脚本和并行 ForEach-Object 执行的 Azure DevOps Azure CLI 任务:失败时无输出

Azure DevOps Azure CLI task with PowerShell script and parallel ForEach-Object execution: no output on failure

为了快速扩展函数应用程序,我们希望能够通过 IaC 部署它们,然后将代码包部署到它上面。不幸的是,这在 Azure DevOps 中使用 YAML 管道是不可能的,所以我不得不求助于使用 Azure CLI。

下面是我想出的 PowerShell 脚本,用于将代码部署到我事先通过 Terraform 部署的函数应用程序池中。为了加快速度,我打开了 ForEach-Object 循环的并行处理,因为单个实例之间没有依赖关系。这在一定程度上也能正常工作,但由于 Azure CLI 的古怪之处,我遇到了麻烦。将非错误信息写入 StdErr 似乎是设计使然。这与其他一些奇怪的行为相结合导致了以下情况:

所以我进退两难。有没有人看到我的实施中的缺陷?非常感谢任何建议。

- task: AzureCLI@2
  displayName: 'Functions deployment'
  env:
    AZURE_CORE_ONLY_SHOW_ERRORS: 'True'
    AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
    ARM_CLIENT_ID: $(AzureApplicationId)
    ARM_CLIENT_SECRET: $(AzureApplicationSecret)
    ARM_SUBSCRIPTION_ID: $(AzureSubscriptionId)
    ARM_TENANT_ID: $(AzureTenantId)
  inputs:
    azureSubscription: 'MySubscription'
    scriptType: 'pscore'
    scriptLocation: 'inlineScript'
    inlineScript: |
      Write-Output -InputObject "INFO: Get Function App names"          
      $appNames = terragrunt output -json all_functionapp_names | ConvertFrom-Json          
      Write-Output -InputObject "INFO: Loop over Function Apps"
      $jobs = $appNames | ForEach-Object -Parallel {          
          $name = $_          
          try              
          {          
              Write-Output -InputObject "INFO: $name`: start slot"        
              az functionapp start --resource-group $(ResourceGroup) --name "$name" --slot Stage --verbose
              Write-Output -InputObject "INFO: $name`: deploy into slot"        
              az functionapp deploy --resource-group $(ResourceGroup) --name "$name" --slot Stage --src-path "$(System.ArtifactsDirectory)/drop/MyCodePackage.zip" --type zip --verbose
              Write-Output -InputObject "INFO: $name`: deploy app settings"          
              az functionapp config appsettings set --resource-group $(ResourceGroup) --name "$name" --slot Stage --settings "@$(Build.ArtifactStagingDirectory)/appsettings.json" --verbose
              Write-Output -InputObject "INFO: $name`: swap slot with production"          
              az functionapp deployment slot swap --resource-group $(ResourceGroup) --name "$name" --slot Stage --action swap --verbose
          }
          catch
          {
              Write-Output -InputObject "ERROR: $name`: An error occured during deployment"
              Write-Output -InputObject ($_.Exception | Format-List -Force)
          }
          finally              
          {
            try
            {
                Write-Output -InputObject "INFO: $name`: stop slot"          
                az functionapp stop --resource-group $(ResourceGroup) --name "$name" --slot Stage --verbose
            }
            catch
            {
                Write-Output -InputObject "ERROR: $name`: could not stop slot"
            }
          }          
      } -AsJob
                          
      [int]$pollingInterval = 10          
      [int]$elapsedSeconds = 0          
      while ($jobs.State -eq "Running") {          
          $jobs.ChildJobs | ForEach-Object {          
              Write-Output -InputObject "---------------------------------"          
              Write-Output -InputObject "INFO: $($_.Name) output [$($elapsedSeconds)s]"          
              Write-Output -InputObject "---------------------------------"          
              $_ | Receive-Job          
              Write-Output -InputObject "---------------------------------"          
              Write-Output -InputObject ""          
          }          
          $elapsedSeconds += $pollingInterval          
          [Threading.Thread]::Sleep($pollingInterval * 1000)          
      }          
      $jobs.ChildJobs | Where-Object { $_.JobStateInfo.State -eq "Failed" } | ForEach-Object {          
          Write-Output -InputObject "ERROR: At least one of the deployments failed with the following reason:"          
          Write-Output -InputObject $_.JobStateInfo.Reason          
      }
                          
      if ($jobs.State -eq "Failed")          
      {          
          exit 1          
      }          
      else          
      {          
          exit 0          
      }
    powerShellErrorActionPreference: 'continue'
    workingDirectory: './infrastructure/environments/$(TerraFormEnvironmentName)'

编辑 1

要从 ChildJobs 获取所有输出,我必须像这样更改代码:

      [int]$pollingInterval = 10          
      [int]$elapsedSeconds = 0          
      $lastResultsRead = false
      while ($jobs.State -eq "Running" -or !$lastResultsRead)
      {          
          $lastResultsRead = $jobs.State -ne "Running"
          $jobs.ChildJobs | ForEach-Object {          
              Write-Output -InputObject "---------------------------------"          
              Write-Output -InputObject "INFO: $($_.Name) output [$($elapsedSeconds)s]"          
              Write-Output -InputObject "---------------------------------"          
              $_ | Receive-Job          
              Write-Output -InputObject "---------------------------------"          
              Write-Output -InputObject ""          
          }          
          $elapsedSeconds += $pollingInterval          
          if (!$lastResultsRead)
          {
              [Threading.Thread]::Sleep($pollingInterval * 1000)
          }

希望这可以帮助每个想要实现类似目标的人。

看来谜底已经解开了

TLDR;

如果您想要正确的错误处理,请从所有 Azure CLI 调用中删除 --verbose,因为即使在设置环境变量 AZURE_CORE_ONLY_SHOW_ERRORS.

说明

我通过向该脚本添加不相关的功能偶然发现了解决方案,并注意到在某些情况下未收集 ChildJobs 的最后输出。我最初认为这是 Azure DevOps 任务的一个怪癖,但发现当我在 VSCode.

中本地调试输出时也会发生这种情况

这导致我为 while 循环添加另一个条件,以确保为我提供最终输出。我将相应地更新初始 post 中的脚本。最后配备了 ChildJobs 中发生的事情的全貌,我设置了一个单独的测试管道,我将 运行 不同的测试用例来找到罪魁祸首。很快我注意到带走 --verbose 可以防止任务失败。无论是否设置 AZURE_CORE_ONLY_SHOW_ERRORS,都会发生这种情况。所以我试了一下 --only-show-errors 选项,它应该与环境变量有相同的结果,尽管只是在一次 Azure CLI 调用中。由于我现在可以使用完整的输出,所以我终于可以看到 --verbose--only-show-errors 不能结合使用的消息。就这样解决了。 --verbose 不得不走了。它所添加的只是命令 运行 多长时间的信息。我想我们可以没有它。

附带说明:与此同时,我发现 ForEach-Object -Parallel {} -AsJob 正在大量使用 PowerShell 运行 空格。这意味着无法以典型方式从 VSCode 中调试它。我发现了一个视频,在这种情况下可能会有所帮助:https://www.youtube.com/watch?v=O-dksknPQBw

我希望这个回答能帮助那些因同样的 st运行ge 行为而绊倒的人。编码愉快。