SimonAx
SimonAx

Reputation: 1378

Dynamically populate DependsOn in Azure DevOps Yaml job

YAML pipelines do not support pre- and post-deploy approvals that can be dynamically added. I've found a way of partially achieving this, see final update to my previous SO question. In summary; I created a YAML template that has 3 jobs; the first and last jobs are conditional jobs that use a ManualValidation@0 depending on a variable in a variable group, and the second job is the actual deployment of a web application.

The next step is to make this approach a more general by introducing a YAML template that takes a jobList as a parameter and conditionally inserts a pre-deploy and post-deploy job. This template looks like

# File: deploy.yml
parameters:
- name: preDeployApprovers
  type: string
  default: ''
- name: postDeployApprovers
  type: string
  default: ''
- name: deployJobs
  type: jobList
  default: []

jobs:
  - ${{ if ne(parameters.preDeployApprovers, '')}}:
    - template: wait-for-manual-approval.yml
      parameters:
        jobName: AwaitPreDeployApproval
        approvers: ${{parameters.preDeployApprovers}}
        instructions: Approve or reject.
        dependsOn:
    
    # Inspired by 
    # https://stackoverflow.com/questions/63256692/is-it-possible-to-pass-a-template-with-a-list-of-jobs-to-a-joblist-type-param
  - ${{ each job in parameters.deployJobs }}:  
    - ${{ each pair in job }}:
        ${{ if ne(pair.key, 'steps') }}:
          ${{ pair.key }}: ${{ pair.value }}
      ${{ if ne(parameters.preDeployApprovers, '')}}:
        dependsOn: [AwaitPreDeployApproval] # POI 1
      steps:
      - ${{ job.steps }} 
      
  - ${{ if ne(parameters.postDeployApprovers, '')}}:
    - template: wait-for-manual-approval.yml
      parameters:
        jobName: AwaitPostDeployApproval
        approvers: ${{parameters.postDeployApprovers}}
        instructions: Approve or reject.
        dependsOn: ${{join(',',parameters.deployJobs)}} # POI 2. Current solution does NOT work

which references wait-for-manual-approval.yml:

#File: wait-for-manual-approval.yml
parameters:
  - name: jobName
    type: string
  - name: approvers
    type: string
    default: ''
  - name: instructions
    type: string
    default: 'Approve or reject'
  - name: dependsOn
    type: object
    default: []
  
jobs:
  - job: ${{parameters.jobName}}
    ${{ if parameters.dependsOn }}:
      dependsOn: ${{ parameters.dependsOn }}
    displayName: Wait for manual approval after deployment
    pool: server
    timeoutInMinutes: 240 #4 hours. 
   
    steps:
    - task: ManualValidation@0        
      condition: ne(variables['${{parameters.approvers}}'], '')
      inputs:
        notifyUsers: |
          $(${{parameters.approvers}})
        instructions: ${{parameters.instructions}}
        onTimeout: reject

The points of interest (POI) are

The way of providing dependsOn as a parameter to the wait-for-manual-approval.yml is heavily inspired by this post.

Point of interest 2 is the source of my headache, it does not work. In the expanded template that I've downloaded there is no dependsOn statement for the post-deploy job. I've tried hard-coding the name of one of the jobs and that works, but for such a template to be useful, I need a way of extracting the names of the jobs that are in the deployJobs parameters. Any suggestions?

Upvotes: 1

Views: 593

Answers (1)

Rui Jarimba
Rui Jarimba

Reputation: 18084

As mentioned by @Alvin Zhao - MSFT, the dependsOn property should be an array [] rather than a string if your job has multiple dependencies.

join function can't be used in this case because:

  • It returns a single string, not an array
  • Complex objects such as a job are converted to an empty string

So instead of:

dependsOn: ${{join(',',parameters.deployJobs)}}

Try using a filtered array:

dependsOn: ${{ parameters.deployJobs.*.job }}

Explanation:

  • parameters.deployJobs.*.job tells the system to operate on parameters.deployJobs as a filtered array and then select the job property

Full working example

trigger: none

parameters:
  - name: deployJobs
    type: jobList
    default:
      - job: A
        displayName: Job A
        steps:
          - script: echo "Hello from Job A"
      - job: B
        displayName: Job B
        steps:
          - script: echo "Hello from Job B"
      - job: C
        displayName: Job C
        steps:
          - script: echo "Hello from Job C"

jobs:
  - ${{ parameters.deployJobs }}

  - job: D
    displayName: Job D
    dependsOn: ${{ parameters.deployJobs.*.job }}
    steps:
      - script: echo "Hello from Job D"

Upvotes: 3

Related Questions