将 Azure Devops 管道变量传递给模板而不将其设置为文字值

Pass Azure Devops pipeline variable to template without it getting set to the literal value

我有一个带有模板的管道,该模板使用 TerraformTestV1@0 规划 terraform

我目前正在根据分支设置一个变量,并希望它设置它使用的变量组,但它正在设置文字值并试图找到具有该值的变量组。 E.G 它正在搜索 $(variable group) 而不是转换。

我试过使用 $[variables.countryCode] 和 $(countryCode) 但得到了相同的结果。我也以同样的方式使用另一个变量,但这个变量正在转换。

我知道 countryCode 变量也已设置,因为我事先有一个 powershell 任务,我测试它显示了我期望的输出。

这是我目前设置的管道

deploy.yml

name: $(BuildDefinitionName)_1.0$(Rev:.r)

trigger:
  tags:
    include:
    - 'refs/tags/uk-*'
    - 'refs/tags/us-*'
    - 'refs/tags/es-*'

pool: Default

variables:
- name: countryCode
  ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags/es-') }}:
    value: es
  ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags/us-') }}:
    value: us
  ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags/uk-') }}:
    value: uk
- name: statefilename
  value: pipelinetest 

resources:
  repositories:
    - repository: templates
      type: git
      name: MyTemplateRepo

stages :
  - stage:
    jobs:
      - job: test
        steps:
              - task: PowerShell@2
                displayName: Split_Tag
                continueOnError: false
                inputs:
                  targetType: inline
                  script: | 
                    Write-host $(countryCode)

  - stage: Plan_Dev
    jobs:
    - template: terraform-plan2.yml@templates
      parameters:
        servicePrincipal: Development $[variables.countryCode]
        stateFileName: "$(statefilename).tfstate"
        variableGroup: Terraform $[variables.countryCode] Development Environment
        terraformVersion: '0.14.6'
        condition: ""

模板:

parameters:
- name: 'servicePrincipal'
  default: 'CI Subscription'
  type: string
- name: 'stateFileName'
  type: string
- name: 'variableGroup'
  type: string
- name: 'condition'
  type: string
- name: 'terraformVersion'
  type: string
  default: "0.14.6"

jobs:
  - job: TerraformPlan
    condition: ${{ parameters.condition }}
    variables:
      - group: ${{ parameters.variableGroup }}
    steps:
          - task: TerraformInstaller@0
            displayName: Terraform Install
            inputs:
                terraformVersion: ${{ parameters.terraformVersion }}
          - task: replacetokens@3
            inputs:
                targetFiles: '**/*.tfvars'
                encoding: 'auto'
                writeBOM: true
                actionOnMissing: 'warn'
                keepToken: false
                tokenPrefix: '#{'
                tokenSuffix: '}'
                useLegacyPattern: false
                enableTransforms: false
                enableTelemetry: true
          - task: TerraformTaskV1@0
            displayName: Terraform init
            inputs:
                provider: 'azurerm'
                command: 'init'
                commandOptions: '-upgrade'
                backendServiceArm: ${{ parameters.servicePrincipal }}
                backendAzureRmResourceGroupName: $(backendResourceGroup)
                backendAzureRmStorageAccountName: $(backendStorageAccount)
                backendAzureRmContainerName: $(backendContainer)
                backendAzureRmKey: "$(short_location).$(tenant).$(short_environment).${{ parameters.stateFileName }}"
          - task: TerraformTaskV1@0
            displayName: Terraform Validate
            inputs:
                provider: 'azurerm'
                command: 'validate'
                backendAzureRmKey: ${{ parameters.servicePrincipal }}
          - task: TerraformTaskV1@0
            displayName: Terraform Plan
            inputs:
                provider: 'azurerm'
                command: 'plan'
                commandOptions: '-var-file="devops.tfvars"'
                environmentServiceNameAzureRM: ${{ parameters.servicePrincipal }}

这是我遇到的错误: 管道无效。作业 TerraformPlan:找不到变量组 Terraform $(countryCode) 开发环境。变量组不存在或未被授权使用。

知道为什么没有转换吗?

您使用错误的语法来引用变量。

$[] 用于 runtime 变量,这意味着在过程中绝对最后设置的变量,after YAML模板已编译。除非您专门处理运行时变量,否则安全的假设是该语法不正确。

您要查找的语法是 ${{ variables.countryCode }}${{ variables['countryCode'] }}。其中任何一个都应该工作。编译时变量引用可能有点棘手......其中一些在某些情况下有效,但在其他情况下无效。

我发现最可靠的语法是 ${{ variable.whatever }}

共有三种语法:

  • 宏语法:$(变量名)

    宏语法在运行时评估。因此,如果您定义变量然后在管道过程中动态更改它,$(variableName) 将始终具有最新值。

  • 运行时表达式:$[表达式]

    运行时表达式很棘手。它们在运行时被评估一次。第一个 few paragraphs in this article 的重要说明:

    Runtime expressions are intended as a way to compute the contents of variables and state

    根据我的解释和过去的经验,运行时表达式必须是等式的整个右侧。举几个例子:

    variables:
      myLiteral: 'hello'
      myVariable: $[ format('{0} world!', variables.myLiteral) ]
    
    steps:
    - powershell: write-host '$(myVariable)'
      condition: $[ contains( variables.myVariable, 'hello') ]
    

    有趣的观察,当用作条件时 $[ ] 不是必需的:

    - powershell: write-host '$(myVariable)'
      condition: contains(variables.myVariable, 'hello')
    
  • 编译时表达式:${{表达式}}

    编译时表达式在管道为 compiling/expanding 时计算。与运行时表达式不同,编译时表达式的使用位置限制较少,因此它们可以出现在文字的中间。例如)

    parameters:
    - name: firstName
      type: string
    
    steps:
    - powershell: Write-Host 'Hello ${{ parameters.firstName }}'
    

    非常重要:只有非常具体的变量在编译时可用!请参阅 Predefined Variables 文章,因为它有一个 table 列出了哪些变量可以在编译时使用。

    同样来自same article,重点是我的:

    The difference between runtime and compile time expression syntaxes is primarily what context is available. In a compile-time expression (${{ <expression> }}), you have access to parameters and statically defined variables. In a runtime expression ($[ <expression> ]), you have access to more variables but no parameters.

    强调静态定义的变量指的是编译时上下文中的变量。看起来你的 countryCode 是 'static' 因为它是在管道中预先声明的,但实际情况是变量的实际值直到编译发生后才被解析。


从您提供的示例来看,它不起作用的原因是您试图将运行时值传递给导致变量组在编译时扩展的模板参数。在编译时,$(countryCode) 的值为 "$(countryCode)" 这就是找不到变量组的原因。

您需要对要传递的参数进行编译时评估:

  - stage: Plan_Dev
    jobs:
    - template: terraform-plan2.yml@templates
      parameters:
        servicePrincipal: Development $[variables.countryCode]
        stateFileName: "$(statefilename).tfstate"
        terraformVersion: '0.14.6'
        condition: ""
        ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags/es-') }}:
           variableGroup: Terraform es Development Environment
        ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags/us-') }}:
           variableGroup: Terraform us Development Environment
        ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags/uk-') }}:
           variableGroup: Terraform uk Development Environment

在类似的主题中,我认为您可能还会发现您的 servicePrincipal 存在相同的问题,因为服务连接和变量组被认为 protected resources 使用在管道运行之前评估的 Approvals+Checks API。您可能希望将主体和变量组移动到模板中,并将 countryCode 作为编译值传递。


更新:

经过更多的实验,我能够在编译时在 pipeline.yml.

中解析 ${{ variables['whatever'] }}${{ variables.whatever }}