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

Posted

技术标签:

【中文标题】如何将复杂的 DevOps 管道模板参数传递给脚本【英文标题】:How to pass complex DevOps pipeline template parameter to script 【发布时间】:2020-05-15 06:14:46 【问题描述】:

在 Azure DevOps 管道模板中,我将参数声明为数组/序列

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。错误消息中引用的行/列对应于我的模板中的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。该时间线/列指的是MY_ENV_VAR: $ parameters.myArray

有没有人遇到过类似的要求,将管道定义中定义的复杂类型(这里是对象的数组/序列)传递给 PowerShell 脚本?如果有,您是如何实现的?

【问题讨论】:

【参考方案1】:

您现在可以使用 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

【讨论】:

这太棒了!如果您尝试将其作为参数传递给 PowerShell 脚本作为整个字符串未通过的参数,我确实遇到了问题。解决方法是将其设置为脚本的环境变量。 我们必须小心使用未记录的函数 - 希望这会成为主流,但它可能会在一夜之间消失! 一种方法可能是用PythonScript@0 替换Bash@3 并使用json 库将参数干净地解压到python 变量中。 谢谢!它对我来说不像 Powershell 那样工作 - 似乎 convertToJson 添加了 Powershell 脚本不喜欢的新行。在我的回答中查看我如何使用 ConvertFrom-Json Powershell 函数解决它。 BTW convertToJson 已经记录在这里:docs.microsoft.com/en-us/azure/devops/pipelines/process/… 编辑现在 convertToJson 现在是主流+记录-谢谢@MaMazav【参考方案2】:

我也面临类似的问题,我的解决方法是使用不同的分隔符将数组扁平化为字符串。

例如,我想让一些参数成为必需参数,如果未传递这些参数,则构建失败,而不是为每个要检查的参数添加一个任务,我想在单个任务中执行此操作。

为此,我首先将一个数组作为参数(传递给另一个名为check-required-params.yml 的模板,该模板负责检查参数的任务),其中每个元素都是name:value 类型的字符串,这是一个用冒号分隔的namevalue 所需参数的串联(使用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

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

【讨论】:

这很好,但是如果没有指定参数,这将失败,因为$env:REQUIRED_PARAMS -split ";" 将返回一个包含空元素的数组。您可以通过快速检查来解决此问题if ([string]::IsNullOrEmpty($env:REQUIRED_PARAMS) -eq $false)【参考方案3】:

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

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

【讨论】:

【参考方案4】:

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

恐怕我们无法将复杂的 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 了解更多详情。

希望这会有所帮助。

【讨论】:

【参考方案5】:

作为@Leo Liu MSFTmentioned in its answer,目前确实不支持,但有人已经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 

【讨论】:

以上是关于如何将复杂的 DevOps 管道模板参数传递给脚本的主要内容,如果未能解决你的问题,请参考以下文章

使用 Azure DevOps 构建管道将机密传递给容器时,Docker BuildKit 返回“/run/secrets/<secret-id>:没有这样的文件或目录”

将参数传递给npm运行脚本

如何将两个参数传递给百里香模板?

如何将模板参数传递给 CRTP?

如何在不覆盖数据源参数的情况下将附加参数传递给剑道模板

如何将数组作为参数传递给另一个脚本?