如何将复杂的 DevOps 管道模板参数传递给脚本

How to pass complex DevOps pipeline template parameter to script

在 Azure DevOps 管道模板中,我将参数声明为 array/sequence

parameters:
  mySubscription: ''
  myArray: []

steps:
- AzureCLI@2
  inputs:
    azureSubscription: ${{ parameters.mySubscription }}
    scriptType: pscore
    scriptPath: $(Build.SourcesDirectory)/script.ps1
    arguments: '-MyYAMLArgument ${{ parameters.myArray }}'

参数的值然后从管道定义传递为

steps:
- template: myTemplate.yml
  parameters:
    mySubscription: 'azure-connection'
    myArray:
    - field1: 'a'
      field2: 'b'
    - field1: 'aa'
      field2: 'bb'

我的问题是我无法按原样在 YAML 语法(类似于 ToString())中传递该数组,以便能够在我的模板中使用和处理来自 PowerShell 的该数组。尝试 运行 此管道时,出现以下错误: /myTemplate.yml (Line: X, Col: X): Unable to convert from Array to String. Value: Array。错误消息中引用的 line/column 对应于我模板中的 arguments: '-MyYAMLArgument ${{ parameters.myArray }}'

我还尝试将参数映射为脚本的环境

- AzureCLI@2
  inputs:
    azureSubscription: ${{ parameters.mySubscription }}
    scriptType: pscore
    scriptPath: $(Build.SourcesDirectory)/script.ps1
    arguments: '-MyYAMLArgument $Env:MY_ENV_VAR'
  env:
    MY_ENV_VAR: ${{ parameters.myArray }}

这也行不通: /myTemplate.yml (Line: X, Col: Y): A sequence was not expected。那个时候line/column指的是MY_ENV_VAR: ${{ parameters.myArray }}.

有没有人遇到过将管道定义中定义的复杂类型(这里是 array/sequence 对象)传递给 PowerShell 脚本的类似要求?如果有,你是如何做到的?

How to pass complex DevOps pipeline template parameter to script

恐怕我们无法将复杂的 DevOps 管道模板参数传递给 PowerShell 脚本。

目前Azure devops的任务只支持一维数组的传输。它不能接受和传输二维数组。虽然我们可以定义一个二维数组的参数,但是我们需要通过脚本来扩展模板中的参数,例如:

- ${{ each field in parameters.myArray}}:

我们可以这样使用它:

- ${{ each step in parameters.buildSteps }}:
  #- ${{ each pair in step }}:

    - task: PowerShell@2
      inputs:
        targetType : inline
        script: |
          Write-Host 'Hello World'

但是我们无法将二维数组直接传递给任务,例如:[field1: 'a', field2: 'b']。这就是您收到错误 Unable to convert from Array to String.

的原因

您可以查看文档 Extend from a template 了解更多详情。

希望对您有所帮助。

@Leo Liu MSFT , this is indeed not supported right now but someone already opened an issue for this improvement .

这个问题还包含一个很好的解决方法,现在可以改用环境变量。此解决方案的缺点是您需要了解数据结构才能正确映射它。

parameters:
  mylist:[]
  #where mylist is a sequence of object matching the mapping:
  #- name: 'the name 1'
  #  value: 'the value of 1'
  #  index: 0
  #- name: 'the name 2'
  #  value: 'the value of 2'
  #  index: 1

env:
  ${{ each item in parameters.mylist }}:
    ${{ format('SCRIPT_PARAM_{0}_KEY', item.index) }}: ${{ item.name }}
    ${{ format('SCRIPT_PARAM_{0}_VAL', item.index) }}: ${{ item.value }}

我也遇到了类似的问题,我的解决方法是对不同维度使用不同的分隔符将字符串中的数组展平。

例如,我想使一些参数成为必需的,如果这些参数没有传递,构建就会失败,而不是为每个参数添加一个任务来检查,我想在一个任务中完成。

为此,我首先将一个数组作为参数(传递给另一个名为 check-required-params.yml 的模板,该模板负责检查参数),其中每个元素都是 name:valuenamevalue 所需参数的连接(使用 format 表达式),用冒号分隔:

# templates/pipeline-template.yml
parameters:
- name: endpoint
  type: string
  default: ''
- name: rootDirectory
  type: string
  default: $(Pipeline.Workspace)
- name: remoteDirectory
  type: string
  default: '/'
- name: archiveName
  type: string
  default: ''
    
#other stuff

      - template: check-required-params.yml
        parameters:
          requiredParams:
          - ${{ format('endpoint:{0}', parameters.endpont) }}
          - ${{ format('archiveName:{0}', parameters.archiveName) }}

然后在 check-required-params.yml 中加入数组,使用表达式 ${{ join(';', parameters.requiredParams) }} 用分号分隔元素,这将创建一个 endpoint:value;archiveName:value 类型的字符串并将其作为环境变量传递.

此时,使用一些字符串操作,在脚本中我可以使用分号作为分隔符来拆分字符串,这样我将得到一个字符串数组,如 name:value 我可以进一步拆分但是这个时间使用冒号作为分隔符。 我的 check-required-params.yml 看起来像:

# templates/check-required-params.yml
parameters:
- name: requiredParams
  type: object
  default: []    
        
steps:
- task: PowerShell@2
  env:
    REQURED_PARAMS: ${{ join(';', parameters.requiredParams) }}
  displayName: Check for required parameters
  inputs:
    targetType: inline
    pwsh: true
    script: |
      $params = $env:REQURED_PARAMS -split ";"
      foreach($param in $params) {
        if ([string]::IsNullOrEmpty($param.Split(":")[1])) {
          Write-Host "##vso[task.logissue type=error;]Missing template parameter $($param.Split(":")[0])"
          Write-Host "##vso[task.complete result=Failed;]"
      }
    }


然后在我的 azure-pipelines.yml 我可以做:

#other stuff
- template: templates/pipeline-template.yml
  parameters:
    endpoint: 'myEndpoint'
    rootDirectory: $(Pipeline.Workspace)/mycode

在此示例中,构建将失败,因为我没有传递参数 archiveName

您可以通过使用变量定义分隔符来增加一些灵活性,而不是在脚本和表达式中进行硬编码

您现在可以在 ADO 管道中使用 convertToJson 函数将这些类型的参数转换为字符串:

parameters:
  - name: myParameter
    type: object
    default:
        name1: value1
        name2: value2

...

- task: Bash@3
  inputs:
    targetType: inline
    script: |
      echo "${{ convertToJson(parameters.myParameter) }}"

参考:https://developercommunity.visualstudio.com/t/allow-type-casting-or-expression-function-from-yam/880210

convertToJson: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#converttojson

基于@ed-randall 的 convertToJson 想法,结合 ConvertFrom-Json Powershell 函数,我们可以使用 JSON 'contract' 在两者之间传递值yaml 和 PS 脚本:

- powershell: |
    $myArray = '${{ convertToJson(parameters.myArray) }}' | ConvertFrom-Json
    ...

脚本文件参数

下面的示例提供了通过参数将 Azure DevOps yaml 布尔值和数组传递到 PowerShell 脚本文件所需的语法。

布尔值 -> 切换
对象 -> 数组

Powershell 脚本

[CmdletBinding()]
param (
    [Parameter()]
    [switch]
    $Check,

    [Parameter()]
    [string[]]
    $Array
)

If($Check.IsPresent)
{
    Write-Host "Check is present"
}
else {
    Write-Host "Check is not present"
}

Write-Host "Next we loop the array..."
Foreach($a in $Array){
    Write-Host "Item in the array: $a"
}

Yaml 管道

trigger: none

pool:
  vmImage: windows-latest

parameters:
  - name: checkBool
    type: boolean

  - name: paramArray
    type: object
    default:
      - one
      - two

steps:
- task: PowerShell@2
  inputs:
    filePath: 'Scripts/DebugSwitches.ps1'
    arguments: -Check:$${{ parameters.checkBool }} -Array ${{ join(', ', parameters.paramArray) }}

布尔语法

请注意,yaml 布尔值通过冒号“:”传递给 PowerShell 开关参数,没有空格。

数组语法

请注意,上面的 yaml 对象数组使用 Join 运算符将数组格式化为逗号分隔数组,并传递给 PowerShell 数组参数。