使用“each”在 Azure DevOps 中构建条件

问题描述 投票:0回答:3

在 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
只能是字符串,我如何利用表达式和/或指令来构造我想要的条件?

所需 YAML 示例

假设为参数给出了以下值

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 yaml azure-pipelines
3个回答
2
投票

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

0
投票

更新:

为了总结您的需求,您正在寻找一个可以在 condition 中使用的表达式,而 dependsOn 值是动态的。并且此阶段仅应在另一组依赖阶段全部跳过后运行。

据我所知和测试,这不能通过each实现。

为了进一步确认,我与我们的管道 PM 讨论了这个场景,他更熟悉 each 和 YAML 管道。

和我一样,他也认为这是不可能实现的。因为

${{ each }}
期望成为映射键或值的 最外层 部分,因此您不能将其嵌套到条件字符串中。


解决方法:

您可以通过依赖于动态列表的硬编码阶段来伪造它,确定它们是否全部被跳过,并设置一个输出变量。那么真正的最终工作将只取决于那个“决策者”工作,其条件将取决于输出变量的内容。

找出所有上游依赖项都被跳过对您来说是一项练习。您也许能够为每个阶段动态构建一个步骤。这些步骤将阶段的状态映射到具有已知名称方案的变量。然后最后(硬编码)步骤迭代已知名称方案的环境变量并决定是否应继续下一阶段。

而且......是的,我知道这听起来有多丑陋


0
投票

@concision 的回复对我有用。我建议对他们的描述进行的唯一更改是更改

and(<PREFIX>${{ join('<PREFIX>, <SUFFIX>', <NAMES>) }}<SUFFIX>)

and(<PREFIX>${{ join('<SUFFIX>, <PREFIX>', <NAMES>) }}<SUFFIX>)

我无法对他们的答案发表评论,因为我需要有 50 的声誉。这就是为什么我将其作为新答案发布

© www.soinside.com 2019 - 2024. All rights reserved.