在Azure DevOps管道中有条件地执行模板

Conditionally execute a template in Azure DevOps Pipeline

提问人:Ben 提问时间:11/16/2023 更新时间:11/20/2023 访问量:90

问:

赏金将在 5 天后到期。这个问题的答案有资格获得 +100 声望赏金。Ben 正在寻找来自信誉良好的来源的答案

我有一个管道,其中包含几个模板步骤,我想根据条件运行这些步骤。如果存在特定的存储库资源,则需要运行这些模板。

为了实现这一目标,我做了:

首先获取对可能存在或不存在的存储库资源的引用。

variables:
  securityRepositoryRef: $[ resources.repositories['security_repo'].ref ]

我的构建步骤:

steps:
- ${{ if contains(variables['securityRepositoryRef'], 'refs') }}:
        - template: security_setup.yml@security_repo
          parameters:
             buildType: 'abcd_1234'

从理论上讲,这将完美地工作,但是 if 语句似乎没有按预期运行。

我尝试了进一步的测试:

steps:
- ${{ if contains(variables['securityRepositoryRef'], 'refs') }}:
    - script: |
        echo securityRepositoryRef contains refs
        echo securityRepositoryRef: $(securityRepositoryRef)
- ${{ else }}:
     - script: |
        echo securityRepositoryRef does not contain refs
        echo securityRepositoryRef: $(securityRepositoryRef)

其输出为:

securityRepositoryRef does not contain refs
securityRepositoryRef: refs/heads/feature/jira-1234
Finishing: CmdLine

这毫无意义。这没有正确执行条件。

为了解决这个问题,我可以使用如下条件:

- script: |
    echo securityRepositoryRef contains refs
    echo securityRepositoryRef : $(securityRepositoryRef)
  condition: contains(variables['securityRepositoryRef'], 'refs')

这在运行时完美运行。我的问题是模板不能有条件。

azure-devops yaml

评论


答:

0赞 Alvin Zhao 11/16/2023 #1

我认为不可能根据存储库资源是否存在/是否在 YAML 管道中定义的条件从存储库资源扩展模板,因为需要确定模板在编译时是否扩展,并且条件不应基于对任何运行时变量的评估, 其值只能在编译时处理

为此,您是否考虑使用其他条件,例如评估如下示例所示的参数?

parameters:
- name: secruity_repo_ref
  type: string
  default: main
  values:
  - main
  - refs/heads/feature/jira-1234
resources:
  repositories:
  - repository: security_repo
    name: Security Repository
    ref: ${{ parameters.secruity_repo_ref }}
    type: git
variables:
  - name: securityRepositoryRef
    value: $[resources.repositories.security_repo.ref]
steps:
- ${{ if eq(parameters.secruity_repo_ref, 'refs/heads/feature/jira-1234') }}:
  - checkout: self
  - checkout: security_repo
  - template: security_setup.yml@security_repo
- ${{ else }}:
  - checkout: self
  - script: |
      echo securityRepository is not used

您可以在运行流水线之前选择参数值,以决定是否展开模板中的步骤。

enter image description here

在每次运行中,存储库资源的元数据都以运行时变量的形式提供给所有作业。

在管道中,模板表达式变量 () 在编译时处理,在运行时启动之前${{ variables.var }}

对于仅在使用模板表达式计算变量时才起作用的条件 incertion (, and ),我们将看到它没有按预期扩展。可以观察到,如果我们 ,它被展开为 而不是 。ifelseifelse${{ <expression> }}resources.repositories.<Alias>.refecho securityRepositoryRef in compile time: ${{ variables.securityRepositoryRef }}resources.repositories.security_repo.refrefs/heads/main

如果我们使用运行时表达式来评估作业阶段部分中的变量以扩展存储库资源中的 YAML 模板,我们需要确保存储库资源存在。否则,由于在编译期间缺少模板,整个管道将无法扩展。$[ <expression> ]resources.repositories.<Alias>.refcondition

因此,根据任何资源的存在来确定是否扩展模板似乎不是一个好主意。

以下是一些文档供您参考。希望分析能有所帮助。

定义变量 - Azure Pipelines |Microsoft 学习

表达式 - Azure Pipelines |Microsoft 学习

定义 Azure Pipelines 的 YAML 资源 - Azure Pipelines |Microsoft 学习

评论

0赞 Ben 11/16/2023
所以,从本质上讲,这是不可能检查的吗?
0赞 Alvin Zhao 11/17/2023
如果使用模板表达式进行检查,则无法检查资源是否存在,因为在编译期间,该值未按预期展开。如果使用运行时表达式进行检查,则必须确保资源存在;然后,就没有意义检查资源是否存在。
0赞 Ben 11/18/2023
好的,有没有更好的方法可以有条件地执行模板,条件是已经定义了存储库资源?
0赞 Alvin Zhao 11/21/2023
您好,我已经为您更新了答案,并为您提供了可能的解决方法。你有没有机会与这个主题下的其他答案一起检查?希望这些建议能满足您的期望。
0赞 Ben 11/21/2023
不幸的是,在这种情况下不会。我的团队控制的 YAML 是许多团队使用的通用模板生成脚本。其他团队定义其资源,并调用具有多个参数的模板。从那里使用参数来构建 maven/gradle/msbuild 等构建。长话短说,我无法控制使用哪些资源,我想有条件运行的代码仅适用于某些团队。参数可以解决此问题,但它需要其他团队进行更改。buildType
0赞 VonC 11/18/2023 #2

Azure DevOps管道的设计在基于存储库资源存在的模板的条件执行方面受到限制:

  • 使用模板表达式 (${{ variables.var }}) 来检查资源是否存在是不可行的。这是因为这些表达式是在编译时计算的,并且在此阶段不会按预期扩展资源的值。
  • 使用运行时表达式 ($[ variables.var ] 需要资源预先存在。但是,这违背了检查资源是否存在的目的,因为必须确保它的存在,管道才能成功运行。

所以问题来了,如何根据仓库资源是否已经定义,有条件地执行一个模板。

您添加了:

security_repo如果尚未定义存储库资源,则不存在。
这是一个通用的构建脚本,无论是否存在存储库资源,都应该能够运行。
security_repo

因此,如果不存在,管道将在编译时失败,因为它找不到存储库中引用的模板 ()。这种情况甚至在评估模板中的条件之前就已经出现。security_reposecurity_setup.yml

确保始终可访问,无论是否存在 。这可能意味着将模板放置在管道始终可用的位置。security_setup.ymlsecurity_repo

使用预验证步骤来检查是否存在相关条件并设置管道变量。
然后,使用此变量有条件地执行通用可访问模板中的步骤。
security_reposecurity_setup.yml

jobs:
- job: PreValidation
  steps:
  - script: |
      # Check for the existence of security_repo or relevant conditions
      # Set a pipeline variable based on the result
      # Example:
      if [ condition ]; then
        echo "##vso[task.setvariable variable=executeSecuritySteps]true"
      else
        echo "##vso[task.setvariable variable=executeSecuritySteps]false"
      fi

- template: security_setup.yml
  parameters:
    executeSecuritySteps: $(executeSecuritySteps)

在模板中:security_setup.yml

parameters:
- name: executeSecuritySteps
  default: false

steps:
- ${{ if eq(parameters.executeSecuritySteps, true) }}:
  - script: |
      # Security steps to be executed

这样可以避免模板存在的限制,因为无论 的状态如何,都可以始终访问 。然后,根据预验证步骤中设置的变量在模板中管理条件。security_setup.ymlsecurity_repo


在您的示例中,我的步骤将包括运行模板而不是脚本。所以同样的行为也会发生,只是更深一层。security_setup.yml

那。。。确实给情况增加了另一层复杂性。核心挑战是有条件地包含基于存储库资源存在的嵌套模板。鉴于Azure DevOps Pipeline的模板扩展和运行时评估的限制,可以考虑使用用于动态生成的预管道脚本:编写一个脚本来检查存储库是否存在。基于此检查,脚本会动态生成整个Azure DevOps管道YAML,包括或排除对嵌套模板的引用。 使用 Azure DevOps REST API 触发使用动态生成的 YAML 内容运行的管道。security_repo

#!/bin/bash

# Check for the existence of 'security_repo'
# Replace with actual logic, such as a git command or API call
if [ -d "/path/to/security_repo" ]; then
    # Include the nested template in the generated YAML
    cat > generated_pipeline.yml <<EOL
steps:
- template: security_setup.yml
EOL
else
    # Exclude the nested template and replace with an alternative step
    cat > generated_pipeline.yml <<EOL
steps:
- script: echo 'No security_repo found'
EOL
fi

# Use Azure DevOps REST API to trigger the pipeline with the generated YAML
# Replace with actual API call, including authentication
curl -X POST -H "Content-Type: application/json" \
     -d @generated_pipeline.yml \
     "https://dev.azure.com/{organization}/{project}/_apis/pipelines/{pipelineId}/runs?api-version=6.0-preview.1"

Azure DevOps管道(示例):security_setup.yml

# security_setup.yml
steps:
- template: nested_template.yml

通过在Azure DevOps外部动态生成整个管道YAML,然后使用此YAML触发管道,可以规避编译时评估的限制。在Azure DevOps处理管道YAML之前,会做出包含或排除嵌套模板的决定。

评论

0赞 Ben 11/22/2023
在您的示例中,我的步骤将包括运行模板而不是脚本。所以同样的行为也会发生,只是更深一层。security_setup.yml
0赞 VonC 11/22/2023
@Ben我已经编辑了答案以解决您的评论。
0赞 Ben 11/22/2023
这对你有用吗?因为这具有我在问题中描述的相同问题行为,所以不会在运行时执行,所以它永远不会运行${{ if eq(parameters.includeNestedTemplate, true) }}nested_template.yml
0赞 VonC 11/22/2023
@Ben真的。我已经修改了解决方案(但未经过测试)
0赞 Kevin Lu-MSFT 11/20/2023 #3

变量:$[ resources.repositories['security_repo'].ref ] 将在运行时扩展。

if 表达式将在编译时扩展。

因此,Repo 资源变量无法成功传递给 if 表达式。

恐怕没有开箱即用的方法可以直接满足你的要求(从 repo 资源中获取 ref 值并在 if 表达式中使用)。

解决方法是,可以在第一个管道中添加 Powershell 任务,以运行 Rest API 以触发管道,并将存储库资源 ref 值传递给第二个管道。第二个管道可以使用参数来接受值并传递给 if 表达式。

参数值可以在编译时传递给 if 表达式。

例如:

第一条管道:

resources:
  repositories:
    - repository: security_repo
      type: git
      ref: main
      name: reponame
pool:
  vmImage: windows-latest

variables:
 securityRepositoryRef: $[ resources.repositories['security_repo'].ref ]

steps:
- task: PowerShell@2
  inputs:
    targetType: 'inline'
    script: |
      $token = "$(pat)"
      echo $newvalue
      $url="https://dev.azure.com/{Orgname}/{Projectname}/_apis/pipelines/{PipelineRunID}/runs?api-version=5.1-preview"
      
      $token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
      
      $JSON = "
      {
        
      
      
        `"resources`": {
          `"repositories`": {
            `"self`": {
              `"ref`": `"refs/heads/master`"
            }
          }
        },
        `"templateParameters`": {
          `"ref`":`"$(securityRepositoryRef)`"
         },
      
      
      
      }"
      
      
      $response = Invoke-RestMethod -Uri $url -Headers @{Authorization = "Basic $token"} -Method Post -Body $JSON -ContentType application/json

第二条管道:

parameters:
- name: ref
  type: string
  default: test


resources:
  repositories:
    - repository: security_repo
      type: git
      ref: ${{ parameters.ref }}
      name: reponame
pool:
  vmImage: ubuntu-latest

steps: 
- ${{ if contains(parameters.ref, 'refs') }}:
  - script: |
      echo securityRepositoryRef contains refs
      echo securityRepositoryRef: ${{ parameters.ref }}
- ${{ else }}:
  - script: |
      echo securityRepositoryRef does not contain refs
      echo securityRepositoryRef: ${{ parameters.ref }}

第二个 Pipeline 将获取目标 ref 值,并检出 ref 对应的 repo 内容

评论

0赞 Ben 11/21/2023
请参阅我在问题中的编辑,了解此处场景的一些背景信息。如果我可以控制您在示例中引用的“第一个管道”脚本,这将很有效,但事实并非如此。