Getting started with Azure DevOps YAML Pipelines (Part 2)
In the previous article, you learned the basics of creating YAML-based pipelines in Azure DevOps. Now somewhere down your journey, you came to a point where:
- You keep writing values repeatedly in your code
- You have logic or a set of tasks that also exist in another pipeline code
- You keep updating your pipeline code when using a different set of values.
Now you ask yourself: is it possible to store these somewhere so I don't have to repeat myself?
Well, yes it's possible. The answers are (assuming the list above are questions):
- Variables
- Runtime Parameters
- Templates
Variables?
Variables, like in your typical programming concept, is a way to referrence a value across multiple parts of your pipeline. This means you only need to change the variable if you need to change the values being used in your pipeline. For example, if you're pipeline performs an API request to a server multiple times across the run, you only need to referrence the URL variable in those tasks, and only change the variable value if the URL changes somehow. No more copy-paste!
If your pipeline looks like this:
pool:
vmImage: 'ubuntu-latest'
steps:
- bash: |
curl https://jsonplaceholder.typicode.com/posts | jq '.[] | select(.userId == 1)'
displayName: 'Query userId 1'
- bash: |
curl https://jsonplaceholder.typicode.com/posts | jq '.[] | select(.userId == 2)'
displayName: 'Query userId 2'
You can change it to look like this instead to make it cleaner and maintainable:
pool:
vmImage: 'ubuntu-latest'
variables:
apiUrl: 'https://jsonplaceholder.typicode.com/posts'
anotherVar: '1'
yetAnotherVar: 2
steps:
- bash: |
curl $(apiUrl) | jq '.[] | select(.userId == 1)'
displayName: 'Query userId 1'
- bash: |
curl $(apiUrl) | jq '.[] | select(.userId == 2)'
displayName: 'Query userId 2'
To declare a variable, you just add variables
to your pipeline, and add the variables in key/value pair format under it. Variables are referenced on your pipeline using the syntax $(varName)
You typically add the variable on the root level, but can declare this anywhere in your pipeline (stage level, job level), but variable scoping applies, so if you have multiple variables of the same name, the more locally scoped value is used (job level variable overrides the value declared at the root level).
pool:
vmImage: 'ubuntu-latest'
variables:
apiUrl: 'https://jsonplaceholder.typicode.com/posts'
anotherVar: '1'
yetAnotherVar: 2
stages:
- stage: GetTODOS
variables:
# this will be used instead since it's locally scoped
apiUrl: 'https://jsonplaceholder.typicode.com/todos'
displayName: Perform
jobs:
- job: GetTODO
displayName: Get TODOs instead
steps:
- bash: |
curl $(apiUrl) | jq '.[] | select(.userId == 1)'
displayName: 'Query userId 1'
- bash: |
curl $(apiUrl) | jq '.[] | select(.userId == 2)'
displayName: 'Query userId 2'
##### THE REST OF THE PIPELINE BELOW #####
Okay, so now you know about variables. But these are read-only, and obviously, if you need to change the values, you need to edit the pipeline and commit again before you make another pipeline run.
How about Runtime Parameters?
Runtime parameters solve the problem of making your variables or values configurable with every run, instead of editing the pipeline file over and over again. When using them, a form is provided run, allowing you to suppply the values right before the pipeline executes.
Adding parameters is a matter of adding the parameters
property on the root level of your config, and adding an object for each parameter you want the pipeline to accept. Referencing them afterwards on the pipeline should look like ${{ parameters.paramName }}
pool:
vmImage: 'ubuntu-latest'
parameters:
- name: apiUrl
default: 'https://jsonplaceholder.typicode.com/posts'
type: string
steps:
- bash: |
curl ${{ parameters.apiUrl }} | jq '.[] | select(.userId == 1)'
displayName: 'Query userId 1'
- bash: |
curl ${{ parameters.apiUrl }} | jq '.[] | select(.userId == 2)'
displayName: 'Query userId 2'
Azure Pipelines accept multiple parameter types, not just string, and you can refer to the official documentation on Runtime Parameters for more examples.
And Templates?
Templates, on the other hand, is a way to reuse parts of your pipeline, or a way to share logic and other similar pipeline aspects to other pipelines. A common use of templates is for inserting tasks and variables, see the following example:
*tasks.yml
steps:
- bash: |
curl https://jsonplaceholder.typicode.com/posts | jq '.[] | select(.userId == 1)'
displayName: 'Query userId 1'
*vars.yml
variables:
anotherVar: '1'
yetAnotherVar: 2
*pipeline.yml
pool:
vmImage: 'ubuntu-latest'
parameters:
- name: apiUrl
default: 'https://jsonplaceholder.typicode.com/posts'
type: string
jobs:
- job: GetTODO
displayName: Get TODOs
steps:
- template: tasks.yml
*pipeline2.yml
pool:
vmImage: 'ubuntu-latest'
variables:
- template: vars.yml
stages:
- stage: GetTODOS
displayName: Perform
jobs:
- job: GetTODO
displayName: Get TODOs instead
steps:
- template: tasks.yml
# you can even reuse templates from the same pipeline
# and add tasks before and after the tasks template
- job: GetTODO2
displayName: Get TODOs instead
steps:
- template: tasks.yml
- bash:
echo $(anotherVar) # should print 1
Resources
- Variables quick guide: https://github.com/microsoft/azure-pipelines-yaml/blob/master/design/variables.md
- Runtime Parameters quick guide: https://github.com/microsoft/azure-pipelines-yaml/blob/master/design/runtime-parameters.md
- Templates official documentation: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops
- Variables official documentation: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch
- Runtime Parameters official documentation: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/runtime-parameters?view=azure-devops&tabs=script
About the author
Geo Dela Paz is a technical writer at OSSPH and a site reliability engineer at IBSS Manila. Feel free to connect with Geo on GitHub, and LinkedIn.