'Use variables in Azure DevOps Pipeline templates

We have a collection of Azure DevOps pipeline templates that we re-use across multiple repositories. Therefore we wanted to have a file that contains variables for all of our templates.

The repo structure looks like this

template repo
  ├── template-1.yml
  ├── template-2.yml
  └── variables.yml

project repo
  ├── ...
  └── azure-pipelines.yml

The variables.yml looks like this

...
variables:
  foo: bar

In template-1.yml we are importing the variables.yml as described in here

variables:
- template: variables.yml

In the azure-pipelines.yml we are using the template like this

resources:
  repositories:
    - repository: build-scripts
      type: git
      name: project-name/build-scripts

steps:
  ...
  - template: template-1.yml@build-scripts
    

When we now try to run the pipeline, we get the following error message:

template-1.yml@build-scripts (Line: 10, Col: 1): Unexpected value 'variables'


Solution 1:[1]

The issue is because you used variable template at steps scope. And variables simply doesn't exists at that level. This should work for you:

resources:
  repositories:
    - repository: build-scripts
      type: git
      name: project-name/build-scripts

variables:
  - template: template-1.yml@build-scripts

steps:
  ...

this is available to use at any place where variables are possible to use. So for instance you can use this in that way:

jobs:
- job: myJob
  timeoutInMinutes: 10
  variables:
  - template: template-1.yml  # Template reference
  pool:
    vmImage: 'ubuntu-16.04'
  steps:
  - script: echo My favorite vegetable is ${{ variables.favoriteVeggie }}.

Solution 2:[2]

If your template file only has variables, you can refer to Krzysztof Madej's answer.

If your template file has both variables and steps as shown below, it can only be used by extends.

# File: template-1.yml
variables: ...

steps: ...

Or you can write them in a stage, as shown below.

# File: template-1.yml
stages:
- stage: {stage}
  variables: ...
  jobs:
  - job: {job}
    steps: ...

Then insert it as a separate stage.

# azure-pipelines.yml
stages:
- stage: ...
- template: template-1.yml

Solution 3:[3]

This approach can help someone, so I decided to post it here.

One more kind of "workaround" for that case, maybe a bit "dirty" since you need to specify the parameters explicitly each time you execute template, which is not a good idea if you have a lot of parameters to pass. (actually, there is a way, read below improved version) But, if you really want or you have not that much parameters, this should work:

the logic is: you have all your parameters in variables template, like templates/vars.yml:

variables:
  - name: myVar1
    value: MyValue1
  - name: myVar2
    value: MyValue2

and since you have everything you need in the variables, probably there is no need to importing variables into template itself, because template will be executed in pipeline, which will have your variables imported and you can substitute it explicitly, like in the example below:

templates/my-template-setup-env.yml's content (without variables inside it):

steps:
  - script: |
      echo "$(myVar3)"

my-azure-pipeline.yml's content (with importing variables template):

name: my-cute-CI-name

trigger:
- main

pool:
  vmImage: ubuntu-18.04
  
variables:
  - template: templates/vars.yml # importing your variables from templates

    stages:
      - stage: RunTheStage
        displayName: 'Run first stage'
        jobs:
        - job: RunTheJob
          displayName: 'Run your job'
          steps:
            - template: templates/my-template-setup-env.yml # your template
               parameters:
                 myVar3: $(myVar1) # substitute value from vars.yml, so myVar1 will be used in templated and printed

Improved version

But, if you have unique naming of your params and variables across all pipelines and templates, you are in safe to not specify it explicitly during template usage, that will work as well:

edited and shortened version of my-azure-pipeline.yml (in case you have the same name of your variable and parameter in template):

variables:
  - template: templates/vars.yml
...
      steps:
        - template: templates/my-template-setup-env.yml # env preparation
          # parameters:
          #   myVar2: $(myVar2) # you don't need to pass any parameters explicitly to the template since you have same name of variable

templates/my-template-setup-env.yml then should be like this:

steps:
  - script: |
      echo "$(myVar2)" # not myVar3, since myVar3 is not in initial variables file templates/vars.yml

or you need to add remaining variables (myVar3 in our first case) into templates/vars.yml file as well.

Solution 4:[4]

Replying as I think there was something missed in the original explanations:

First: If you are referencing variables in a template then you must ensure that you are extending that template when you call it. Calling a template that defines variables that is not extended will result in a failed pipeline execution.

template-1.yml@build-scripts (Line: 10, Col: 1): Unexpected value 'variables'

Second: When referencing variables in multiple templates do not use:

${{ variables.foo }} #even though this is seen in templates examples.

but rather use the normal variable syntax

$(foo) #this works 

So for the originally reference example the following should work.

Variables.yml:

  variables:
    foo: bar

template-1.yml:

variables:
- template: variables.yml
....
steps:
...
- task: CmdLine@2
  displayName: Display Variable
  inputs:
    script: |
      echo $(foo)

azure-pipelines.yml:

resources:
  repositories:
    - repository: build-scripts
      type: git
      name: project-name/build-scripts

extends:
  template: template-1.yml@build-scripts

template-2.yml

steps:
...
- task: CmdLine@2
  displayName: Display Variable
  inputs:
    script: |
      echo $(foo)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Jane Ma-MSFT
Solution 3
Solution 4 Mc1brew