Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typecast from YAML Object to String #427

Closed
JoeGaggler opened this issue Jan 9, 2020 · 9 comments
Closed

Typecast from YAML Object to String #427

JoeGaggler opened this issue Jan 9, 2020 · 9 comments

Comments

@JoeGaggler
Copy link

Expressions in YAML pipelines may be literals such as ​String, Boolean, and Number, but also any ​YAML object, for example the ​template parameters object is a complex type with members for each parameter.

It would be useful to be able to consume entire YAML objects in YAML tasks such as the PowerShell task. The current options for passing in parameters require strings (often environment variables), which means it is required to somehow "stringify" or serialize YAML objects into Strings.

There currently is no ​type cast from YAML Object to String, or similarly no such ​expression function.

Alternatively, an explicit expression function could be added in case an implicit type cast is not appropriate.

@ggirard07
Copy link

Really looking forward for this.
Here is my original issue https://stackoverflow.com/questions/59972329/how-to-pass-complex-devops-pipeline-template-parameter-to-script

Forcing to iterate a collection from the template means I have to restart the same task over and over again. When using script related task which takes a long time to setup/authenticate, like AzureCLI, you are wasting a lot of time (almost 30sec per task run) just to wait for it to complete its initialization. So for a collection of 11 items, this is 300sec or 5 minutes lost just to initialize thing.
By having the possibility to pass YAML sequences as a string to the script task, we can have the task initialized only once and let it deals with the sequence by parsing the YAML itself!

Also, this allows to perform data validation from the scripting language on the YAML object to ensure the task is being used properly and return proper error message, which cannot be done currently.

@JoeGaggler
Copy link
Author

FWIW, I am currently using a hack to manually enumerate the array in order to "stringify" arrays into environment variables to be used in PowerShell scripts. You can try to adapt this sample YAML to reconstruct a complex object. Here I am transferring the mylist YAML object coming from a template parameter so that it can be used in PowerShell:

- task: PowerShell@2
  inputs:
    targetType: 'inline'
    script: |
        $indexes = @("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
        $params = $indexes | %{ 
            @{i=$_; KeyName="SCRIPT_PARAM_$($_)_KEY"; ValName="SCRIPT_PARAM_$($_)_VAL"} | % {
                $ParamName = (Get-Item env:$($_.KeyName) -ErrorAction SilentlyContinue).Value
                $ParamValue = (Get-Item env:$($_.ValName) -ErrorAction SilentlyContinue).Value
              
                if ($ParamName) {
                    @{ParamName=$ParamName;ParamValue=$ParamValue}
                }
            }
        }
        ### $params is now an array of key-value pairs
  env:
        ${{ if parameters['mylist'] }}:
            ${{ each item in parameters['mylist'] }}:
                ${{ if eq(parameters['mylist'][0]['name'], item['name']) }}:
                    SCRIPT_PARAM_0_KEY: ${{ item['name'] }}
                    SCRIPT_PARAM_0_VAL: ${{ item['value'] }}
                ${{ if eq(parameters['mylist'][1]['name'], item['name']) }}:
                    SCRIPT_PARAM_1_KEY: ${{ item['name'] }}
                    SCRIPT_PARAM_1_VAL: ${{ item['value'] }}
                ${{ if eq(parameters['mylist'][2]['name'], item['name']) }}:
                    SCRIPT_PARAM_2_KEY: ${{ item['name'] }}
                    SCRIPT_PARAM_2_VAL: ${{ item['value'] }}
            # Repeat hack for indexes > 2

@ggirard07
Copy link

@JoeGaggler Nice hack, haven't thought about using the ${{ each }} statement to define my script env variables.
Perhaps a possible way to simplify it further could be to include the index directly in the original structure in the meantime as key (left operator) can use parameters in template expression compared to macro and runtime expression (see table). This should end-up looking like:

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

But this still remains a pain to implement, especially if your structure has multiple nested levels.
This also does not allow to validate the input data and pipeline will simply crash when requesting a new run with an ultra generic error message...

@stale
Copy link

stale bot commented Apr 25, 2020

In order to consolidate to fewer feedback channels, we've moved suggestions and issue reporting to Developer Community. Sorry for any confusion resulting from this move.

@stale stale bot closed this as completed Apr 26, 2020
@PaulVrugt
Copy link

for anyone who (like me) would like to know to what issue this was actually moved:
https://developercommunity.visualstudio.com/idea/880210/allow-type-casting-or-expression-function-from-yam.html

@guidooliveira
Copy link

@JoeGaggler Nice hack, haven't thought about using the ${{ each }} statement to define my script env variables. Perhaps a possible way to simplify it further could be to include the index directly in the original structure in the meantime as key (left operator) can use parameters in template expression compared to macro and runtime expression (see table). This should end-up looking like:

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

But this still remains a pain to implement, especially if your structure has multiple nested levels. This also does not allow to validate the input data and pipeline will simply crash when requesting a new run with an ultra generic error message...

another way would be to pass the parameter converted into json and using ConvertFrom-Json to turn it into an object

  • pwsh: |
    $jsonObject = "${{ convertToJson(parameters.complex) }}" | ConvertFrom-Json

@guidooliveira
Copy link

@JoeGaggler Nice hack, haven't thought about using the ${{ each }} statement to define my script env variables. Perhaps a possible way to simplify it further could be to include the index directly in the original structure in the meantime as key (left operator) can use parameters in template expression compared to macro and runtime expression (see table). This should end-up looking like:

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

But this still remains a pain to implement, especially if your structure has multiple nested levels. This also does not allow to validate the input data and pipeline will simply crash when requesting a new run with an ultra generic error message...

another way would be to pass the parameter converted into json and

- pwsh: |
    $jsonObject = "${{ convertToJson(parameters.complex) }}" | Convertfrom-Json

@Henderson42
Copy link

I just wanted to pop a note here that I found easiest way to get this working was following the last point in @guidooliveira's latest comment.

parameters:
  - name: identityProviders
    type: object
    default:
      - authority: https://an.authority.com/blah/blah
        clientId: 12345
        clientSecret: notasecretyet
        autoProvisionUser: 1
        name: customer-oidc
        displayName: customer SSO
        validateIssuer: true
        ssoGroups:
        - databaseId: 00000000-0000-0000-0000-000000000000
          GroupName: Administrators
          SSOGroupName: product-sysadmins
        - databaseId: 99999999-9999-9999-9999-999999999999
          GroupName: Administrators
          SSOGroupName: product-admins
      - authority: https://an.authority.com/blah/blah2
        clientId: 123452
        clientSecret: notasecretyet2
        autoProvisionUser: 1
        name: customer-oidc2
        displayName: customer SSO2
        validateIssuer: true
        ssoGroups:
        - databaseId: 00000000-0000-0000-0000-000000000000
          GroupName: Administrators2
          SSOGroupName: product-sysadmins2
        - databaseId: 99999999-9999-9999-9999-999999999999
          AutoRekGroupName: Administrators2
          SSOGroupName: product-admins2

To use it I used Powershell@2 task:

steps:
  - ${{ each idp in parameters.identityProviders }}:
    - task: PowerShell@2
      name: BuildXml${{idp.clientId}}
      inputs:
        targetType: inline
        pwsh: true
        script: |
          $inputJson = '${{ convertToJson(idp) }}'
          $identityProviders = ConvertFrom-Json -Depth 100 -InputObject $inputJson

From here it's easy enough to handle the object as a Powershell object within the script.

N.B. I'm not entirely sure why but if I wrapped the line with quotes rather than apostrophes:

$inputJson = "${{ convertToJson(idp) }}"

I'd get an error:

At D:\a\_temp\a14f50cb-846f-499a-9dcb-83df77f43fd9.ps1:5 char:6
+     "authority": "[https://an.authority.com/blah/blah",](https://an.authority.com/blah/blah%22,)
+      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unexpected token 'authority": "[https://an.authority.com/blah/blah",](https://an.authority.com/blah/blah%22,)

@vladimirs-davidovs
Copy link

vladimirs-davidovs commented Jan 27, 2023

I think you are getting a problem with quotes, because Json, you are enclosing in those, is unescaped, if you escape it, it should work fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants