0

I need to read settings from a Json file for the execution of a pipeline. I use a Powershell task for this. In the next step I want to use those properties as input. However when I do that the variable does not get rendered. How can I achieve this?

- task: PowerShell@2
  displayName: 'Main -> Read App Setting'
  inputs:
    targetType: 'inline'
    script: |
      $settings = Get-Content "$(Build.Repository.LocalPath)\settings\appsettings.json" | ConvertFrom-Json
      Write-Host "##vso[task.setvariable variable=type]$($settings.pipeline.type)"      
  
- template: ./.lib/create_resources.yml
  parameters:
    type: $(type)
Bastiaan
  • 686
  • 1
  • 8
  • 20
  • 3
    try `Write-Host "##vso[task.setvariable variable=type]$($settings.pipeline.type)"` – Shayki Abramczyk Jun 24 '20 at 11:57
  • @ShaykiAbramczyk if you make this a bit longer and explain why you have to use the `$($variable)` syntax (which is called `subexpression syntax`), it would be a good answer for the question. – FoxDeploy Jun 24 '20 at 13:28
  • @FoxDeploy I will explain if it works for him, I'm not sure this is the issue. – Shayki Abramczyk Jun 24 '20 at 13:54
  • @Shayki Abramczyk Good catch, as it was not working did not catch this. I have updated the question, to avoid confusion about the actual problem. Thanks – Bastiaan Jun 25 '20 at 07:13

2 Answers2

1

However when I do that the variable does not get rendered. How can I achieve this?

You can't pass parameter to template in this way. The variable did not get rendered because we can't use runtime variables in template parameters. It's the direct cause why it didn't get rendered.

This is by design of Azure Devops Service, check Process the pipeline:

To turn a pipeline into a run, Azure Pipelines goes through several steps in this order:

1.First, expand templates and evaluate template expressions.

2.Next, evaluate dependencies at the stage level to pick the first stage(s) to run.

3.For each stage selected to run, two things happen: 
All resources used in all jobs are gathered up and validated for authorization to run.
Evaluate dependencies at the job level to pick the first job(s) to run.

4.For each job selected to run, expand multi-configs (strategy: matrix or strategy: parallel in YAML) into multiple runtime jobs.

5.For each runtime job, evaluate conditions to decide whether that job is eligible to run.

6.Request an agent for each eligible runtime job.

So your variable comes after the task is executed(step6) while the parameter is evaluated at the very first of this process(step1 of pipeline run process). See:

This ordering helps answer a common question: why can't I use certain variables in my template parameters? Step 1, template expansion, operates solely on the text of the YAML document. Runtime variables don't exist during that step. After step 1, template parameters have been completely resolved and no longer exist.

In addition: Shayki Abramczyk's comment is right though it's not the main cause of your issue, you should use the Write-Host "##vso[task.setvariable variable=type]$($settings.pipeline.type)" so that the value of $(type) would be $settings.pipeline.type instead of $settings.

To use variable in next step:

We can check this document:

In order to use a variable as a task input, you must make the variable an output variable, and you must give the producing task a reference name.

More details about how to use that in yaml pipeline please check my another issue.

LoLance
  • 25,666
  • 1
  • 39
  • 73
  • The ${{parameters.type}} do not work as input for a task, however they do display when used in a PS script in the template like echo ${{parameters.type}} – Bastiaan Jun 25 '20 at 07:39
  • Additionally, how would I get a setup where I use templates like functions with input variables / parameters, that are called from a main yml file, whereby the main yml file reads the json file from the repo and sends the properties to the templates? – Bastiaan Jun 25 '20 at 07:53
  • For your first comment: You're passing `$(type)` to parameter `type`, since template parameter is evaluated before runtime variables, so when you use `${{parameters.type}}` as task input, it won't work, it should be plain text. When you use that in script, it becomes ${{parameters.type}}=>`$(type)`, since $(type) is a runtime variable, it can be evaluated in script, so you can echo its value in script. This is the behavior you met. – LoLance Jun 25 '20 at 08:01
  • If you only want to share variables across steps/tasks, [job-scoped variable](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#set-a-job-scoped-variable-from-a-script) is enough. If you want to use the variable as next steps' condition, you can check [this](https://stackoverflow.com/a/61998701/10910450). – LoLance Jun 25 '20 at 08:07
  • No, it actually displays the updated value nicely in the PS script. It only does not work when used as a value for a step input – Bastiaan Jun 25 '20 at 08:10
  • To use that as input, please check my another issue [here](https://stackoverflow.com/a/61118034/10910450). – LoLance Jun 25 '20 at 08:11
  • @Bastiaan Answer updated, let me know if it can't work for you. – LoLance Jun 25 '20 at 08:20
  • @Li-MSF, I think I have a solution for this using PowerShell script to convert the parameters to variables that can be used throughout the template. Is there any way we can discuss this, without polluting this thread? – Bastiaan Jun 25 '20 at 09:05
  • Cheers! Feel free to share it as answer if you've found the workaround, it can help members with similar issue! – LoLance Jun 25 '20 at 09:06
  • @Li-MSF, cool, I did! Let me know if this is a proper solution from your perspective? – Bastiaan Jun 25 '20 at 09:17
  • @Bastiaan It looks correct if you have the special reason to pass one variable from first-level template, then second-levl template. But if all you need is to share the variable to different template files and use the variable in script instead of input, set the variable in azure-pipelines.yml and all tasks among the templates can use the variable easily via $(type). It depends on your needs. Ps: Glad to know you find a way working for you, don't forget to accept it as self-answer after 48 hours ~ – LoLance Jun 25 '20 at 09:33
  • @Li-MSF, thanks a lot. This exercise made me understand the setup fully, so I'll probably rethink the solution a bit, thanks for helping out! – Bastiaan Jun 25 '20 at 09:52
0

As Lance described the problem is that template parameters are expanded first, so setting the parameters using a variable does not work when used as input for a tasks in the template. However I found out that in a PowerShell task in the template, the exact same parameters are showing the latest value.

Additionally I wanted to run templates sequential and have the output of template 1 be used for template 2 etc, without template1 knowing about template2, no dependencies. This way individual templates can still be run manually from DevOps.

So I used the following setup, which fulfills both requirements. It passes the output from template 1 to template 2, without hardcoding those variables in the templates itself & it shows template 1 passing an incoming parameter as input for a seconds step by using PowerShell to convert the parameter to a variable first. This way one could add a PowerShell script to each template and convert all parameters to variables and use those variables in the template instead. One extra step, but until MS includes dynamic parameters, quite acceptable I think.

Main:

steps:
- task: PowerShell@2
  displayName: 'Main -> Read App Setting'
  inputs:
    targetType: 'inline'
    script: |
      $settings = Get-Content "$(Build.Repository.LocalPath)\settings\appsettings.json" | ConvertFrom-Json
      Write-Host "##vso[task.setvariable variable=type]$($settings.pipeline.type)"

- template: ./template1.yml
  parameters:
    input: $(type)
    outputVariable: template1Output

- template: ./template2.yml
  parameters:
    input: $(template1Output)

Template1:

parameters:
- name: input
  type: string
- name: outputVariable
  type: string

steps:
  
- task: PowerShell@2
  displayName: Task in Template 1
  inputs:
    targetType: inline
    script: |
      $input = "${{parameters.input}}"
      $output = $input + " some extra value"
      Write-Host "##vso[task.setvariable variable=input]${{parameters.input}}"
      Write-Host "##vso[task.setvariable variable=${{parameters.outputVariable}}]$output"

- template: ./template2.yml
  parameters:
    input: $(input)

Template2:

parameters:
- name: input
  type: string

steps:
  
- task: PowerShell@2
  displayName: Task in Template 2
  inputs:
    targetType: 'inline'
    script: |
      echo "${{parameters.input}}"
Bastiaan
  • 686
  • 1
  • 8
  • 20
  • Hi friend, please consider accepting it as answer so that more members with similar issue can easily find the useful info from marked answer. Just a reminder of [can i ...](https://stackoverflow.blog/2009/01/06/accept-your-own-answers/) – LoLance Jun 30 '20 at 09:17