How to pass complex DevOps pipeline template parameter to script

自作多情 提交于 2020-06-27 15:05:09

问题


In an Azure DevOps pipeline template, I am declaring a parameter as an array/sequence

parameters:
  mySubscription: ''
  myArray: []

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

Value for the parameter is then passed from pipeline definition as

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

My problem is I can't pass that array as-is in YAML syntax (kind of ToString()) to be able to consume and treat that array from PowerShell in my template. When trying to run this pipeline, I get the following error: /myTemplate.yml (Line: X, Col: X): Unable to convert from Array to String. Value: Array. The line/column referenced in the error message correspond to arguments: '-MyYAMLArgument ${{ parameters.myArray }}' from my template.

I also tried to map the parameter as an environment for my script

- 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 }}

This does not work too: /myTemplate.yml (Line: X, Col: Y): A sequence was not expected. That time line/column refers to MY_ENV_VAR: ${{ parameters.myArray }}.

Does anyone ever faced a similar requirement to pass complex types (here an array/sequence of object) defined from the pipeline definition to a PowerShell script? If so, how did you achieve it?


回答1:


How to pass complex DevOps pipeline template parameter to script

I am afraid we could not pass complex DevOps pipeline template parameters to a PowerShell script.

Currently, the task of Azure devops only supports the transfer of one-dimensional arrays. It cannot accept and transfer two-dimensional arrays. Although we can define the parameters of a two-dimensional array, but we need to extend the parameters from a template by the scripts like:

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

We could use it like:

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

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

But we could not pass the two-dimensional arrays directly to the task like: [field1: 'a', field2: 'b']. That the reason why you got the error Unable to convert from Array to String.

You could check document Extend from a template for some more details.

Hope this helps.




回答2:


I'm also facing a similar problem, my workaround is to flatten the array in a string using different separator for different dimensions.

For example I want to make some parameters required and fail the build if these parameters are not passed, instead of add a task for every parameter to check, I want to do this in a single task.

To do this I first pass, as a parameter (to another template, called check-required-params.yml which hold the task responsible for check the parameters), an array where each element is a string of the type name:value which is a concatenation (using the format expression) of the name and the value of required parameters separated by a colon:

# 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) }}

Then in check-required-params.yml I join the array separating the elements with a semicolon using the expression ${{ join(';', parameters.requiredParams) }}, this create a string of the type endpoint:value;archiveName:value and pass this as an environmental variable.

At this point, using a little of string manipulations, in a script I can split the string using the semicolon as separator so I will get an array of strings like name:value which I can further split but this time using colon as separator. My check-required-params.yml looks like:

# templates/check-required-params.yml
parameters:
- name: requiredParams
  type: object
  default: []    

steps:
- task: PowerShell@2
  inputs:
  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;]"
      }
    }
  targetType: inline
  pwsh: true
  env:
    REQURED_PARAMS: ${{ join(';', parameters.requiredParams) }}
  displayName: Check for required parameters

Then in my azure-pipelines.yml I can do:

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

In this example the build will fail because i don't pass the parameter archiveName

You can add some flexibility by using variables also for defining the separators instead of hardcoding in the scripts and in the expressions




回答3:


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

This issue also contains a good workaround for now to use environment variables instead. Draw back of this solution is you need to be aware of the data structure in order to map it properly.

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 }}


来源:https://stackoverflow.com/questions/59972329/how-to-pass-complex-devops-pipeline-template-parameter-to-script

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!