Search code examples
azure-devopsazure-pipelinesazure-pipelines-yaml

convertJson doesn't escape values from variables when using macro syntax?


Consider the following Azure yaml pipeline and template that uses the convertToJson function to convert an object parameter to a json string:

parameters:
  - name: foo
    type: string
    default: 'b"ar'

trigger: none

pool:
  vmImage: 'ubuntu-latest'

variables:
  - name: foo
    value: 'b"ar'

steps:
  - checkout: none
  - template: /pipelines/steps/set-settings.yaml
    parameters:
      settings:
        fooLiteral: 'b"ar'
        fooParameter: ${{ parameters.foo }}
        fooCompiledVariable: ${{ variables.foo }}
        fooRuntimeVariable: $(foo)

Template /pipelines/steps/set-settings.yaml:

parameters:
  - name: settings
    type: object

steps:
  - script: |
      echo $SETTINGS
    displayName: 'Display settings'
    env:
      SETTINGS: ${{ convertToJson(parameters.settings) }}

When running the pipeline, why do all values get properly escaped except fooRuntimeVariable?

Pipeline logs


Solution

  • Test the same YAML sample and I can reproduce the same issue.

    The root cause of the issue is that the macro variable($(foo)) is not expand when the expression: ConvertToJson executes.

    When the ConvertToJson expression executes, fooRuntimeVariable will keep the value of $(foo) instead of expand the value to 'b"ar'.

    So its actual conversion result:

    { "fooLiteral": "b\"ar", "fooParameter": "b\"ar", "fooCompiledVariable": "b\"ar", "fooRuntimeVariable": "$(foo)" }
    

    After you print it in the script task, the $(foo) will expand to the value: 'b"ar'.

    { "fooLiteral": "b\"ar", "fooParameter": "b\"ar", "fooCompiledVariable": "b\"ar", "fooRuntimeVariable": "b"ar" }
    

    To prove this, I added condition to the template to check the conversion result:

    Here is my example:

    Template YAML:

    parameters:
      - name: settings
        type: object
    
    steps:
    
    - script: |
        echo $SETTINGS
      displayName: 'Display settings'
      condition: eq('${{ convertToJson(parameters.settings) }}', 'b"ar')
      env:
          SETTINGS: ${{ convertToJson(parameters.settings) }}
    

    Main YAML:

    parameters:
      - name: foo
        type: string
        default: b"ar
    
    trigger: none
    
    pool:
      vmImage: 'Ubuntu-latest'
    
    variables:
      - name: foo
        value: b"ar
    
    steps:
      - checkout: none
      
      - template: test.yml
        parameters:
          settings:
            fooLiteral: b"ar
            fooParameter: ${{ parameters.foo }}
            fooCompiledVariable: ${{ variables.foo }}
            fooRuntimeVariable: $(foo)
    

    When we run the Pipeline, the Display settings task will skip. Then we can check the condition result: it will show the $(foo) for the fooRuntimeVariable.

    enter image description here

    To sum up, when we use ConvertToJson Expression, I suggest that we can use compile time expressions (${{xxx}}) to use variables.