提问人:Ben 提问时间:11/16/2023 更新时间:11/20/2023 访问量:90
在Azure DevOps管道中有条件地执行模板
Conditionally execute a template in Azure DevOps Pipeline
问:
我有一个管道,其中包含几个模板步骤,我想根据条件运行这些步骤。如果存在特定的存储库资源,则需要运行这些模板。
为了实现这一目标,我做了:
首先获取对可能存在或不存在的存储库资源的引用。
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')
这在运行时完美运行。我的问题是模板不能有条件。
答:
我认为不可能根据存储库资源是否存在/是否在 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
您可以在运行流水线之前选择参数值,以决定是否展开模板中的步骤。
在每次运行中,存储库资源的元数据都以运行时变量的形式提供给所有作业。
在管道中,模板表达式变量 () 在编译时处理,在运行时启动之前。
${{ variables.var }}
对于仅在使用模板表达式计算变量时才起作用的条件 incertion (, and ),我们将看到它没有按预期扩展。可以观察到,如果我们 ,它被展开为 而不是 。if
elseif
else
${{ <expression> }}
resources.repositories.<Alias>.ref
echo securityRepositoryRef in compile time: ${{ variables.securityRepositoryRef }}
resources.repositories.security_repo.ref
refs/heads/main
如果我们使用运行时表达式来评估作业阶段部分中的变量以扩展存储库资源中的 YAML 模板,我们需要确保存储库资源存在。否则,由于在编译期间缺少模板,整个管道将无法扩展。$[ <expression> ]
resources.repositories.<Alias>.ref
condition
因此,根据任何资源的存在来确定是否扩展模板似乎不是一个好主意。
以下是一些文档供您参考。希望分析能有所帮助。
定义变量 - Azure Pipelines |Microsoft 学习
表达式 - Azure Pipelines |Microsoft 学习
定义 Azure Pipelines 的 YAML 资源 - Azure Pipelines |Microsoft 学习
评论
buildType
Azure DevOps管道的设计在基于存储库资源存在的模板的条件执行方面受到限制:
- 使用模板表达式 (
${{ variables.var }}
) 来检查资源是否存在是不可行的。这是因为这些表达式是在编译时计算的,并且在此阶段不会按预期扩展资源的值。 - 使用运行时表达式 (
$[ variables.var ]
) 需要资源预先存在。但是,这违背了检查资源是否存在的目的,因为必须确保它的存在,管道才能成功运行。
所以问题来了,如何根据仓库资源是否已经定义,有条件地执行一个模板。
您添加了:
security_repo
如果尚未定义存储库资源,则不存在。
这是一个通用的构建脚本,无论是否存在存储库资源,都应该能够运行。security_repo
因此,如果不存在,管道将在编译时失败,因为它找不到存储库中引用的模板 ()。这种情况甚至在评估模板中的条件之前就已经出现。security_repo
security_setup.yml
确保始终可访问,无论是否存在 。这可能意味着将模板放置在管道始终可用的位置。security_setup.yml
security_repo
使用预验证步骤来检查是否存在相关条件并设置管道变量。
然后,使用此变量有条件地执行通用可访问模板中的步骤。security_repo
security_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.yml
security_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之前,会做出包含或排除嵌套模板的决定。
评论
security_setup.yml
${{ if eq(parameters.includeNestedTemplate, true) }}
nested_template.yml
变量:$[ 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 内容
评论