如何在Azure DevOps YAML管道的变量组中使用IF ELSE?

76

我想把一个变量分配为两个值之一,除了变量组还需要使用IF ELSE语句,但找不到相关参考。

基本上我需要将这个Jenkins的逻辑转换为Azure DevOps。

Jenkins

if (branch = 'master') { 
   env = 'a'
} else if (branch = 'dev'){
    env ='b'
}

我在以下链接中找到了1个参考文献,但是如果变量部分没有变量组,则此链接似乎有效。

https://dev59.com/21MH5IYBdhLWcg3w7Vvx#57532526

但是在我的流水线中,我已经有一个用于secrets的变量组,因此我必须使用名称/值约定,但是示例与诸如expected a mappingA mapping was not expectedUnexpected value 'env'等错误不兼容。

variables:
- group: my-global
- name: env
  value:
    ${{ if eq(variables['Build.SourceBranchName'], 'master') }}: 
      env: a
    ${{ if eq(variables['Build.SourceBranchName'], 'dev') }}: 
      env: b

或者
variables:
- group: my-global
- name: env
  value:
    ${{ if eq(variables['Build.SourceBranchName'], 'master') }}: a
    ${{ if eq(variables['Build.SourceBranchName'], 'dev') }}: b


我现在的一个解决方法是将带有表达式的变量部分复制到所有需要的作业中。但我想将其简化为全局变量。 - kevmando
如果还有其他人在寻找此问题的答案,请参考以下更好的回答:https://dev59.com/21MH5IYBdhLWcg3w7Vvx?noredirect=1&lq=1 - Bojan Kogoj
1
你尝试过这样的代码吗?condition: ${{eq(parameters.target, 'BuildBackEnd') }} - Сергей
使用'Build.SourceBranchName'时必须小心。如果您使用分支名称,例如feature/adding_a_new_widget,则分支名称将抓取最后一个'/'之后的字符串。也就是说,您的SourceBranchName = adding_a_new_widget... 根据我的经验,最佳实践是使用...(contains['Build.SourceBranch'], 'refs/heads/dev') - 或者feature、testing等...'main和master'不太容易出现这个问题,但大多数其他分支... - CamBeeler
7个回答

71

这段代码可以工作。
我正在尝试使用参数进行类似操作。

variables:
  - name: var1
    ${{ if eq(parameters.var1, 'custom') }}:
      value: $(var1.manual.custom)
    ${{ if ne(parameters.var1, 'custom') }}:
      value: ${{ parameters.var1 }}

1
我是作者。 :) 它特定于具有变量组的if-else。您可以在OP中间看到if-else示例链接。 - kevmando
2
你好!我正在尝试实现类似作者的一些功能。但在 Azure 的在线编辑器中,此语法会给我一个“意外属性 ${{ if .... }}” 的错误提示。即便我仍然保存了该文件并点击“验证”/“运行”按钮,也会弹出一个窗口显示“'value' 已经定义”。 - Hellium
谢谢!这很棒,是根据参数添加条件变量的好方法。 - Mert Alnuaimi
@Hellium,我刚刚遇到了同样的问题('value'已经定义),现在已经解决了。请检查每个你的“if”是否都有一个以“else”块结尾,如果你有多个条件,则需要为每个条件使用“elseif”,并以“else”结束。希望这能帮助到某些人。 - stranger
如果我们想要添加第三种情况,即如果前两个if不匹配,则值必须为abc,该怎么办? - Shivani
显示剩余3条评论

68

更新于2021年9月9日

我们现在原生支持if else表达式,可以像这样编写:

variables:
- group: PROD
- name: env
  ${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
    value: a
  ${{ else }}:
    value: b

steps:
- script: | 
    echo '$(name)'
    echo '$(env)'

原始回复

使用模板表达式 ${{ if ...... }} 的语法不仅限于作业/阶段级别。下面的两个流水线都可以完成相同的操作并生成相同的输出:

stages:
- stage: One
  displayName: Build and restore
  variables:
  - group: PROD
  - name: env
    ${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
      value: a
    ${{ if eq(variables['Build.SourceBranchName'], 'dev') }}:
      value: b
  jobs:
  - job: A
    steps:
    - script: | 
        echo '$(name)'
        echo '$(env)'
variables:
- group: PROD
- name: env
  ${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
    value: a
  ${{ if eq(variables['Build.SourceBranchName'], 'dev') }}:
    value: b

steps:
- script: | 
    echo '$(name)'
    echo '$(env)'

1
这是我认为最好的解决方案。在我的情况下,Visual Studio中的行是红色的,但一切都运行得非常完美。 - MaxThom
1
需要提到编译时表达式和运行时表达式之间的区别。https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions编译时表达式为 a: ${{ <expression> }},而运行时表达式为 b: $[ <expression> ]。因此,在分支引用中应使用运行时表达式。 - Serj
如果它是一个本地变量或在变量组中定义的变量,我们该如何做相同的事情?我正在尝试这样做 - ${{ if eq(variables['shouldRunMigrations'], 'True') }}:但这不起作用。 - VivekDev
如果我们有更多的条件需要使用 and 运算符,应该如何编写呢?我尝试了这个 ${{ if and(eq(parameters.environment, 'others'), ne(parameters.others_environment, 'prod')}}: 但是它并没有起作用。 - inquisitive
And 支持多个参数。你在这里错过了闭合括号 ${{ if and(eq(parameters.environment, 'others'), ne(parameters.others_environment, 'prod'))}}: - Krzysztof Madej
echo $(name) 的原因是什么?只是为了演示它是空的吗?还是包含某些神奇的值? - Siete

16

微软几周前发布了一个新功能,用于YAML管道,只需使用IF ELSE符号即可实现条件表达式。

https://learn.microsoft.com/zh-cn/azure/devops/release-notes/2021/sprint-192-update#new-yaml-conditional-expressions

通过使用 ${{ else }} 和 ${{ elseif }} 表达式,编写 YAML 文件中的条件表达式变得更加容易。以下是在 YAML 管道文件中如何使用这些表达式的示例。

steps:
- script: tool
  env:
    ${{ if parameters.debug }}:
      TOOL_DEBUG: true
      TOOL_DEBUG_DIR: _dbg
    ${{ else }}:
      TOOL_DEBUG: false
      TOOL_DEBUG_DIR: _dbg
variables:
  ${{ if eq(parameters.os, 'win') }}:
    testsFolder: windows
  ${{ elseif eq(parameters.os, 'linux' }}:
    testsFolder: linux
  ${{ else }}:
    testsFolder: mac

11

我希望能够进行运行时条件评估,类似于编译时的操作:

variables:
   VERBOSE_FLAG:
   ${{if variables['System.Debug']}}:
      value: '--verbose'
   ${{else}}:
      value: ''

但不幸的是,Azure DevOps不支持特殊类型的函数,比如if(condition, then case, else case) - 所以我尝试了一下,发现可以使用replace函数进行双重字符串替换。当然,这看起来有点hacky。

例如,一个人可能想要根据系统调试是否启用来调整任务输入。这不能使用“标准条件插入”(${{ if … }}:)来完成,因为System.Debug在模板表达式中不在范围内。所以,运行时表达式来拯救:

- job:
  variables:
    VERBOSE_FLAG: $[
        replace(
          replace(
            eq(lower(variables['System.Debug']), 'true'),
            True,
            '--verbose'
          ),
          False,
          ''
        )
      ]
  steps:
    - task: cURLUploader@2
      inputs:
        # …
        options: --fail --more-curl-flags $(VERBOSE_FLAG)

请注意,在调用replace之前使用eq来检查System.Debug的值并不是多余的:由于eq始终返回TrueFalse,因此我们可以安全地使用replace将这些值分别映射到'--verbose'''
总的来说,我强烈建议将布尔表达式(例如应用布尔值function,如eqgtin)作为内部replace应用的第一个参数。如果没有这样做,而是像下面这样简单地编写:
        replace(
          replace(
            lower(variables['System.Debug']),
            'true',
            '--verbose'
          ),
          'false',
          ''
        )

那么,如果System.Debug被设置为例如footruebarVERBOSE_FLAG的值将变为foo--verbosebar


太好了!对于“system.debug”与标准条件不兼容的问题,您提供了一个很好的解决方案和说明。我搜索了很长时间才找到这个技巧,并将其用于根据 Azure pipeline 的调试标志设置我的变量$(buildConfiguration)为_Debug_或_Release_。简而言之: buildConfiguration: $[ replace( replace(lower(variables['system.debug']), 'false', 'Release'), 'true', 'Debug' )] - Beauty
谢谢!这是唯一有点可行的解决方案。https://github.com/MicrosoftDocs/azure-devops-docs/issues/6970#issuecomment-1119444334 - Tomáš Fejfar
很棒,虽然有点难读,但对我来说运行良好。 - Benj

4

我认为目前你需要使用任务来自定义带有名称/值语法变量和条件变量值。正如你所指出的,名称/值语法的对象结构会破坏表达式的解析。

对我来说,以下是一个相当干净的实现,如果你想将其从管道中抽象出来,似乎一个简单的模板供你的多个管道使用应该满足对中央“全局”位置的需求。

variables:
  - group: FakeVarGroup
  - name: env
    value: dev

steps:
  - powershell: |
      if ($env:Build_SourceBranchName -eq 'master') {
        Write-Host ##vso[task.setvariable variable=env;isOutput=true]a
        return
      } else {
        Write-Host ##vso[task.setvariable variable=env;isOutput=true]b
      }      
    displayName: "Set Env Value"

2
谢谢您的建议。我想我在这里看到过类似的解决方案,但由于希望有更简单的解决方案而没有尝试。我也向微软支持团队提出了问题,如果他们没有解决方案,那么我会尝试这个。 - kevmando
我曾与微软支持团队交换了电子邮件,他们最终建议我采用这种解决方法,并要求我为微软开发社区输入功能建议。因此,我现在将把这个标记为答案。 - kevmando
@kevmando,您可以发布一条功能建议的链接吗? - Nick Graham
1
@NickGraham,这是它的链接。 https://developercommunity.visualstudio.com/content/idea/848155/support-conditional-expression-in-variables-even-w.html 但是这里的新回复似乎现在可以工作了? https://dev59.com/uVMH5IYBdhLWcg3wpgzz#64017113 - kevmando

1
据我所知,实现条件分支构建的最佳方式是在您的YAML中使用"trigger",而不是实现复杂的"if-else"。这样做也更加安全,并且您可以更明确地控制分支触发器,而不是依赖CI变量。
例如:
# specific branch build 
jobs:
- job: buildmaster
  pool:
    vmImage: 'vs2017-win2016'
  trigger:
    - master

  steps:
  - script: |
      echo "trigger for master branch"

- job: buildfeature
  pool:
    vmImage: 'vs2017-win2016'
  trigger:
    - feature

  steps:
  - script: |
      echo "trigger for feature branch"


要使用包含和排除分支的触发器,您可以使用更复杂的带有包含和排除分支的触发器语法。
示例:
# specific branch build
trigger:
  branches:
    include:
    - master
    - releases/*
    exclude:
    - releases/1.*

Azure DevOps Pipelines的官方文档中关于YAML中trigger的说明为: Azure Pipelines YAML trigger documentation 更新1: 我在此重新发布我的评论并附加注释: 我考虑使用不同的流水线,因为在CI变量之间跳转的复杂性不如在一个带有触发器的YAML中具有多个作业来维护。具有触发器的多个作业也强制我们在分支管理上进行清晰的区分和规定。由于这些可维护性优势,我的团队已经使用触发器和条件分支包含了一年的时间。
如果您持不同意见,请随意反驳,但对我来说,在任何步骤中嵌入逻辑以检查当前正在使用哪个分支,然后执行任何进一步的操作,更像是临时解决方案。这在以前给我和我的团队带来了维护问题。
特别是如果嵌入式逻辑倾向于通过检查其他分支来增长,那么后期复杂性比分支之间有明确的分离更加复杂。此外,如果YAML文件将长时间维护,它应该在不同分支之间具有清晰的规定和路线图。冗余是不可避免的,但分离特定逻辑的意图将在长期维护中产生更多回报。这就是为什么我在我的答案中也强调了分支包含和排除的原因 :)

谢谢您的建议,但我的流水线已经使用触发器来实现不同的目的了。说实话,与Jenkins相比,触发器功能本身似乎也不太灵活。很难实现取消重复的pr/分支构建等条件。此外,如果我必须为每个分支重复相同的作业以更改单个值,那么将会有太多冗余行。(我也可以通过重复变量部分来解决这个问题,这将是较少冗余代码的方法。) - kevmando
新管道?不需要。那个YAML文件在一个文件中,因此它可以用于一个管道,可以服务于许多分支。 - Eriawan Kusumawardhono
OP并不是在询问如何基于分支值触发构建。问题是如何根据触发管道的上下文使变量值有条件地变化。当应用于实际问题时,您提供的解决方案会强制OP创建两个管道:1)触发=主干&环境=a 2)触发=dev&环境=b。 - Josh Gust
@JoshGust 是的,我会尽量避免在Azure DevOps中使用模板功能,因为这将会重蹈自编写Jenkins库地狱的覆辙。有时候,少一些重复使用反而会让事情更加简单 :) - kevmando
我已经更新了我的答案,以反映我在上面的评论中所关注的问题。 - Eriawan Kusumawardhono
显示剩余4条评论

0
Azure YAML if-else 解决方案(当您定义了一个需要名称/值表示法的group后,随后使用)。
variables:
- group: my-global
- name: env
  value: a       # set by default
- name: env
  ${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
  value: b     # will override default

如果你没有定义一个group

variables:
 env: a       # set by default
 ${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
 env: b      # will override default

管道使用此解决方案时会出现“变量已定义”的错误。 - Anup Ramachandran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接