Bob
Bob

Reputation: 415

ADO YAML Templates - how to handle small number of platform-dependent tasks?

I am in the process of porting some existing Classic ADO pipelines to YAML. There are two separate Classic pipelines for Windows and Linux, but, because I have been able to switch most scripting to bash, I am close to having a common cross-platform YAML pipeline.

Nevertheless, I still have a few platform-dependent tasks interspersed between the majority platform-independent tasks. Of these few tasks, some only need to run on Windows and don't exist for Linux, and the remainder exist in two platform-specific versions of the tasks - one using bash and the other batch or PowerShell.

My hope was to make the bulk of the script into a template with an isWindows parameter, and to use this parameters to control the platform-dependent parts. This is roughly what I have, but it is not working:

trigger: none

pool:
  name: BuildPool
  demands:
  - Agent.OS -equals Windows_NT

extends:
  template: common-template.yml
  parameters:
    isWindows: true

Then common-template.yml itself. Note that the approach here, using condition, does not work. Although I have omitted most of the cross-platform tasks, these form the majority of the pipeline - there are only a few tasks that need special handling.

parameters:
- name: isWindows
  type: boolean

jobs:
- job: MyJob

  steps:
  - checkout: none
    clean: true

  # Simple example of cross-platform script task
  - bash: |
      env
    displayName: Print Environment

  # ... more cross platform tasks

  # Windows only task
  - task: CmdLine@2
    condition: eq('${{ parameters.isWindows }}', 'true')
    inputs:
      filename: scripts\windows_only.bat

  # ... more cross platform tasks

  # Task with specialization for each platform
  # WINDOWS version
  - task: CmdLine@2
    condition: eq('${{ parameters.isWindows }}', 'true')
    inputs:
      filename: scripts\task_a.bat
  # LINUX version
  - task: Bash@3
    condition: eq('${{ parameters.isWindows }}', 'false')
    inputs:
      filePath: scripts/task_a.sh

  # ... more cross platform tasks

The issue is that when I try to run with a Linux agent I get this error:

No agent found in pool <pool name> satisfies both of the following demands: Agent.OS, Cmd. All demands: Agent.OS -equals Linux, Cmd, Agent.Version ...

I assume this is because CmdLine tasks are present, even though they are "turned off" via a condition. I assume the dependency on the task is probably determined before the condition is ever evaluated.

Is what I am trying to do possible? Is there another approach? I am not very experienced with ADO and this is the first time I have tried anything with templates so I am possibly missing something straightforward.

Upvotes: 1

Views: 209

Answers (2)

Bob
Bob

Reputation: 415

After digging into the ADO docs a bit, I discovered that what I needed was called Conditional Insertion:

parameters:
- name: isWindows
  type: boolean

jobs:
- job: MyJob

  ...

  # Windows only task
  - ${{ if parameters.isWindows }}:
    - task: CmdLine@2
      inputs:
        filename: scripts\windows_only.bat

  # Task with specialization for each platform
  - ${{ if parameters.isWindows }}:
    # WINDOWS version
    - task: CmdLine@2
      inputs:
        filename: scripts\task_a.bat
  - $ {{ else }}:
    # LINUX version
    - task: Bash@3
      inputs:
        filePath: scripts/task_a.sh

  ...

There were a few tricky things that might be worth highlighting:

  1. The "conditions" act as items in the YAML list of tasks. Hence there is a need to prefix with - .
  2. The actual task that is protected by the condition is then indented a further level with respect to the condition line.
  3. Don't forget the colon at the end of the condition.
  4. The syntax I showed above doesn't actually work for me - I got an error about using else. It turned out that the else syntax is a feature of the 2022 release of ADO and we are stuck on the 2020 release. So in my case I had to introduce inverted tests: ${{ if not(parameters.isWindows) }}:
  5. I got quite confused about how to test for true values. Various examples in the documentation, when talking about expressions in the condition field of a task, use syntax like: condition: eq(variables.isSomeCondition, 'true'). Note the comparison against a string value. I initially copied this in the inclusion expressions but found that both ${{ if eq(parameters.isWindows, 'true') }}: and ${{ if eq(parameters.isWindows, 'false') }}: triggered when the parameter itself was true. Clearly, the strings 'true' and 'false' evaluate to a boolean true in this context. It's not that this doesn't make sense - it is the inconsistency with the documented examples of the condition syntax that caught me out.

Upvotes: 1

Chris
Chris

Reputation: 1009

You can use PowerShell steps instead of batch/bash (PowerShell can be installed on both Windows and Linux).

You can also remove the demands and just use the predefined Agent.OS variable in your conditions for tasks which require specific OS:

- powershell: 'scripts/windows_only.ps1'
  condition: eq(variables['Agent.OS', 'Windows_NT')

Upvotes: 1

Related Questions