disastrs :: present! :: notes

In this guide I will describe how to write an azure pipeline, The different parts of an azure pipeline, Gotchas to be aware of, and so on.

It is my hope that by writing this, I can spare you much frustration copy-pasting pre-existing azure pipelines and stack-overflow answers that you don't understand

What is an azure pipeline

An azure pipeline is basically a way of telling Azure DevOps that you would - like to run some process - using azure infrastructure

Here are some common use-cases for azure pipelines: - Build automation – testing, building, and pushing a docker image to a container registry - Managing deployments on a kubernetes cluster, or other cloud computing platform - Performing scheduled database maintenance - Miscellaneous automation of Azure infrastructure – using Azure service endpoints

Anatomy of an azure pipeline

I generally like to think of an azure pipeline in two parts:

The frontmatter contains declarations for your trigger, pool, variables, parameters, and so on

The steps are where the actual work happens – these are your your tasks, scripts, etc.

Syntax of Frontmatter

99% of my pipelines have frontmatter like this:

  pool:
    vmImage: ubuntu-latest
  variables:
    - name: foo
      value: bar
    - name: duck
      value: soup
  # use parameters for more complex objects, for example
  # I often use these instead of variables if I need to create a pipeline using iteration.
  parameters:
    - name: 'environments'
      type: object
      default:
        - name: development
          namespace: dev
          filename: DevFileNameOne.txt
        - name: production
          namespace: prod
          filename: ProductionFilenameTwo.txt

Syntax of The Work

The Work contains the work that is “run” during an azure pipeline run. Depending on how complex your pipeline is, it may be organized like so:

Each subdivision here can be thought of as an enclosing scope, from the most gross (pipeline), to the most granular (step).

This is an important point to get. Here’s the heirarchy again:

Pipeline > Stage > Job > Step

Here is a very bare example of the syntax used:

  stages:
    - stage: Build
    jobs:
      - job:
      displayName: "Build Job A"
      steps:
        - task: ...
        - script: ...
        - stage: Deploy
        ...

If your pipeline is simple (one stage, one job), then you may drop the enclosing stages/jobs declaration and just define an array named steps:

  steps:
    - task: ...
    - task: ...
    - script: ...

Variable and Parameter reference

I’m lifting this straight from the docs:

There are different ways of referencing/expanding variables, according to when you need the variable expanded

Macro syntax - $() - is used to expand variables at run-time, just before a task executes. When a variable is not found, the expression is rendered as-is - i.e., $(foo) remains $(foo) if the pipeline has no such variable.

Template Expression syntax - ${{ }} - is used to expand variables at compile-time. It can also be used to expand pipeline parameters, which are not modifiable at run-time. Because of this, variables expanded with ${{ }} may be used as structural elements of a pipeline – i.e. as keys as well as values.

If a template expression has no value (e.g. in the case a non-existent variable is referenced), then the compiler silently renders it with an empty string at compile-time

Runtime Expression syntax - $[ ] - is used to evaluate expressions at run-time. This is commonly used for conditional execution of jobs or stages

I usually use $(var), but it might be better to use ${{variables.var}} unless I’m explicitly referencing a variable I’m expecting to set during runtime.

Here's a handy table, which I stole:

Syntax Example When is it processed? Where does it expand in a pipeline definition? How does it render when not found?
macro $(var) runtime before a task executes value (right side) prints $(var)
template expression ${{ variables.var }} compile time key or value (left or right side) empty string
runtime expression $[variables.var] runtime value (right side) empty string

Setting variables between steps

Sometimes you need to "export" a variable from one step to another. You can use logging commands for this.

steps:
  - script: |
      echo "##vso[task.setvariable variable=hey_there]hot_stuff"
  - script: |
      echo $(hey_there) # outputs "hot_stuff"

However, some variables are read-only. These are:

Such variables must be referenced using macro syntax or runtime expression syntax.

Remember that if a variable is not defined at compile time, template expressions will be empty

Template string operations

Maybe you want to do forbidden magic. I understand.

Interpolating parameters into a script

Use convertToJson to: convert to json

Handy if you know your way around jq

parameters:
- name: 'environments'
  type: object
  default:
  - environment: development
    namespace: dev
  - environment: production
    namespace: prod
steps:
  script: |
    printf '${{ convertToJson(parameters.environments) }}'