The company I'm at is investigating whether we can migrate from classic Azure DevOps Builds and Releases towards YAML Pipelines. Using these pipelines we want to deploy to four four different environments (dev, test, acc, prod). On a regular basis we need to validate a feature that has not made it into the main branch, i.e. we deploy a feature branch to dev and/or test. Whenever the main branch is updated, the latest version of main is automatically deployed to dev and test, which is problematic when we're in the process of testing the feature branch. Our solution with the classic pipelines is to temporary add a pre-deploy approval.
YAML pipelines do not have pre- and post-approvals, instead one has to add an approval to an environment. This has been asked and answered on SO. In our company, this approach has a major drawback: We have 10-20 pipelines that continuously deploy to dev and test, and by adding an approval to the dev environment for the sake of testing a single application, we halt ALL deployments to dev. Manually approving the the deployments of applications that do not interfere with the ongoing test activities is tedious at best.
Recently I discovered the task ManualValidation@0, which might present a solution to the aforementioned problem, but it too comes with some challenges. The manual validation should on default be skipped, only to be activated when some environmental variable or variable from a Library is set. Also, the step has to be added to each YAML pipeline making them bloated. Before continuing on that path, I wonder whether others have come up with a better solution.
---------------------------- Solution ----------------------------
Based on the recommendations by @Alvin Zhao - MSFT, I came to a solution that I'd like to share.
The company I'm at has several websites that are deployed in (practically) the same way, i.e. the same steps are taken. It is therefore worth investing in a Git repository that contains the shared steps. Here is an excerpt of the deploy template:
#file deploy-website.yml
parameters:
# some params omitted for brevity
- name: preDeployApproversVariable #POI 1
type: string
default: ''
- name: postDeployApprovalVariable
type: string
default: ''
jobs:
- ${{ if ne(parameters.preDeployApproversVariable, '')}}: #POI 2
- template: wait-for-manual-approval.yml
parameters
jobName: AwaitPreDeployApproval
approvers: ${{parameters.preDeployApproversVariable}}
instructions: Approve or reject deploying
dependsOn: #POI 3
- deployment: WebDeployment
${{ if ne(parameters.preDeployApproversVariable,'') }} #POI 4
dependsOn: [AwaitPreDeployApproval]
strategy:
runOnce:
#The actual deployment steps are listed here
- ${{ if ne(parameters.postDeployApprovalVariable, '')}}:
- template: wait-for-manual-approval.yml
parameters:
jobName: AwaitPostDeployApproval
approvers: ${{parameters.postDeployApprovalVariable}}
instructions: Approve or reject deploying
dependsOn:
- WebDeployment
There are some several points of interestest (POI):
preDeployApproversVariable
and postDeployApproversVariable
are parameters containing the name of a variable retrieved from a variable group, they must not contain the value.dependsOn
, which for the pre-approval can be left empty, for the post-approval it's probably better to add a value.dependsOn
on the pre-approval job.#File: wait-for-manual-approval.yml
parameters:
- name: jobName #POI 5
type: string
- name: approvers
type: string
- name: instructions
type: string
default: 'Approve or reject'
- name: dependsOn
type: object
jobs:
- job: '${{paramters.jobName}}'
${{ if parameters.dependsOn}}: #POI 6
dependsOn: '${{parameters.dependsOn}}'
pool: server
timeoutInMinutes: 60
steps:
- task: ManualValidation@0
condition: ne(variables['${{parameters.approvers}}'], '') #POI 7
inputs:
notifyUsers: |
$(${{parameters.approvers}}) #POI 8
instructions: ${{parameters.instructions}}
onTimeout: reject
The points of interest are:
approvers
is expanded at compile time and must therefore be the name of a variable that comes from e.g. a variable group. If the value of this variable in the variable group is left empty, the condition is not met and the ManualValidation@0
is skipped.Finally, the pipeline looks like this:
#omitted
resources:
repositories:
- repository: CommonTemplates
type: git
name: Git_repo_name
stage: Development
jobs:
- template: deploy-website.yml@CommonTemplates
parameters:
preDeployApprovers: PreDeployApproverApp1
postDeployApprovers: PostDeployApproverApp1
Based on the discussions, the workaround to add the condition
property like eq(variables['Build.SourceBranchName'], 'feature')
or eq(variables['PreDeployApprovalRequired'], 'true')
for the ManualValidation@1
task can help skip approval checking according to the branch from which the source code is consumed, or the variable value set in the variable group.
Based on the current descriptions, it doesn't seem necessary to add approval checks for those environment resources, but you may try adding approval checks for a variable group resource.
Taking the YAML pipeline below for example, it will reference the variable group Feature Deployment
on the condition ${{ if eq(variables['Build.SourceBranchName'], 'feature') }}
, and will keep waiting for the approval to use this variable group.
trigger:
- main
- feature
variables:
- ${{ if eq(variables['Build.SourceBranchName'], 'feature') }}:
- group: Development
- ${{ if eq(variables['Build.SourceBranchName'], 'main') }}:
- group: MainDevelopment
steps:
- checkout: self
- script: |
echo $(PreDeployApprovalRequired)
With that being said, it still requires you to create another variable group for the YAML definition from main
branch to reference to avoid pending approval when deploying the code from main
branch.
See more information on Pipeline deployment approvals - Azure Pipelines | Microsoft Learn.