在 Azure DevOps(YAML 管道)中,我们有一个阶段只能在跳过另一组阶段后运行。
在下面的示例中,参数
copyStages_UAT
可以由用户在触发手动运行时修改,这意味着无法对dependsOn
和condition
属性进行硬编码,因此需要使用指令each
。
- template: ../Stages/stage--code--depoly-to-environment.yml
parameters:
name: Deploy_PRD_UKS
displayName: Deploy PRD - UK South
dependsOn:
- ${{ each uatStage in parameters.copyStages_UAT }}:
- Roll_Back_${{ uatStage.name }}
variables:
- template: ../Variables/variables--code--global.yml
- template: ../Variables/variables--code--prd.yml
environment: PRD
上面的这个阶段在管道中工作,但是因为成功运行会导致
dependsOn
中定义的阶段被跳过,遗憾的是Azure DevOps也将跳过这个阶段。
为了解决这个问题,我尝试添加一个
condition
来检查前面的阶段是否全部被跳过。
condition: >-
and(replace(
${{ each uatStage in parameters.copyStages_UAT }}:
eq(dependencies.Roll_Back_${{ uatStage.name }}.result, 'Skipped'),
), ', )', ' )')
不幸的是,我似乎无法在这种情况下使用指令
each
-
在此上下文中不允许使用指令“each”。嵌入字符串中的表达式不支持指令。仅当整个值是表达式时才支持指令。
由于
condition
只能是字符串,我如何利用表达式和/或指令来构造我想要的条件?
假设为参数给出了以下值
copyStages_UAT
-
- name: UAT_UKS
displayName: UAT - UK South
- name: UAT_UKW
displayName: UAT - UK West
这就是 YAML 的编译方式。我不担心条件的格式,只要包含相关的检查即可。
- template: ../Stages/stage--code--depoly-to-environment.yml
parameters:
name: Deploy_PRD_UKS
displayName: Deploy PRD - UK South
dependsOn:
- Roll_Back_UAT_UKS
- Roll_Back_UAT_UKW
condition: >-
and(
eq(dependencies.Roll_Back_UAT_UKS.result, 'Skipped'),
eq(dependencies.Roll_Back_UAT_UKW.result, 'Skipped')
)
variables:
- template: ../Variables/variables--code--global.yml
- template: ../Variables/variables--code--prd.yml
environment: PRD
Azure DevOps Pipelines 没有特别好的方法来解决这个问题。不过,
and(...)
,
join(delimiter, ...)
和 “过滤数组” 可以用来巧妙地完成此操作。
观察以下条件可以重新排列:
and(
eq(dependencies.Roll_Back_UAT_UKW.result, 'Skipped'),
eq(dependencies.Roll_Back_UAT_UKX.result, 'Skipped'),
eq(dependencies.Roll_Back_UAT_UKY.result, 'Skipped'),
eq(dependencies.Roll_Back_UAT_UKZ.result, 'Skipped')
)
and(
eq(dependencies.Roll_Back_
UAT_UKW
.result, 'Skipped'), eq(dependencies.Roll_Back_
UAT_UKX
.result, 'Skipped'), eq(dependencies.Roll_Back_
UAT_UKY
.result, 'Skipped'), eq(dependencies.Roll_Back_
UAT_UKZ
.result, 'Skipped')
)
或更抽象地说,其中
PREFIX=eq(dependencies.Roll_Back_
和 SUFFIX=.result, 'Skipped')
:
and(
<PREFIX>
UAT_UKW
<SUFFIX>, <PREFIX>
UAT_UKX
<SUFFIX>, <PREFIX>
UAT_UKY
<SUFFIX>, <PREFIX>
UAT_UKZ
<SUFFIX>
)
使用过滤数组 (
NAMES=parameters.parameterName.*.name
) 来提取名称,然后可以编写聚合:
and(<PREFIX>${{ join('<PREFIX>, <SUFFIX>', <NAMES>) }}<SUFFIX>)
因此:
condition: |
and(
ne(dependencies.Roll_Back_${{ join('.result, ''Skipped''), ne(dependencies.Roll_Back_', parameters.copyStages_UAT.*.name) }}.result, 'Skipped')
)
但是有一些明显的警告:
如果
parameters.copyStages_UAT
中有 0 个元素,则表达式的计算结果将是 and(dependencies.Roll_Back_.result, 'Skipped')
,这可能是无意义的。
and(...)
需要至少 2 个参数,因此如果只有 0 或 1 个表达式,则可能会失败。为了避免这种情况,可以提供 True
作为第一个和第二个参数,以便表达式始终有效。如果您的逻辑需要 or(...)
,请使用 False
而不是 True
以保持含义一致。
因此,您可能需要通过修改检查来防止出现这些情况:
${{ if eq(length(parameters.copyStages_UAT), 0) }}:
condition: false
${{ else }}:
condition: |
and(
True,
ne(dependencies.Roll_Back_${{ join('.result, ''Skipped''), ne(dependencies.Roll_Back_', parameters.copyStages_UAT.*.name) }}.result, 'Skipped')
)
更新:
为了总结您的需求,您正在寻找一个可以在 condition 中使用的表达式,而 dependsOn 值是动态的。并且此阶段仅应在另一组依赖阶段全部跳过后运行。
据我所知和测试,这不能通过each实现。
为了进一步确认,我与我们的管道 PM 讨论了这个场景,他更熟悉 each 和 YAML 管道。
和我一样,他也认为这是不可能实现的。因为
${{ each }}
期望成为映射键或值的 最外层 部分,因此您不能将其嵌套到条件字符串中。
解决方法:
您可以通过依赖于动态列表的硬编码阶段来伪造它,确定它们是否全部被跳过,并设置一个输出变量。那么真正的最终工作将只取决于那个“决策者”工作,其条件将取决于输出变量的内容。
找出所有上游依赖项都被跳过对您来说是一项练习。您也许能够为每个阶段动态构建一个步骤。这些步骤将阶段的状态映射到具有已知名称方案的变量。然后最后(硬编码)步骤迭代已知名称方案的环境变量并决定是否应继续下一阶段。
而且......是的,我知道这听起来有多丑陋☹
@concision 的回复对我有用。我建议对他们的描述进行的唯一更改是更改
and(<PREFIX>${{ join('<PREFIX>, <SUFFIX>', <NAMES>) }}<SUFFIX>)
由
and(<PREFIX>${{ join('<SUFFIX>, <PREFIX>', <NAMES>) }}<SUFFIX>)
我无法对他们的答案发表评论,因为我需要有 50 的声誉。这就是为什么我将其作为新答案发布