Adding pull-request comments to Azure DevOps Repo from Azure DevOps Pipelines

Sometimes as part of your pull request process, you may want to include outputs, checks, or other relevant information as comments after your pipeline runs on various stages and tasks. In this blog post we will look and how this can be achieved using a bash task and Azure DevOps API

Prerequisites

I will be using a service connection within my pipeline that will be running the task to write a comment using the Azure DevOps API. This connection will need to have the configuration: Contribute to pull requests set to Allow for the specific Azure DevOps repository

The Bash script

As mentioned in the above, I will be using bash to interact with the Azure DevOps API, here is the script:

COMMENT=$(echo "Test PR comment from Azure DevOps Pipeline")

ADO_API=$(echo "$(System.CollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.Name)/pullRequests/$(System.PullRequest.PullRequestId)/threads?api-version=7.1-preview.1")

PR_COMMENT=$(jq --arg comment "$COMMENT" '.comments[0].content = $comment' <<< '{"comments": [{"parentCommentId": 0,"content": "","commentType": 1}],"status": 1}')

curl --request POST "$ADO_API" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "Authorization: Bearer $SYSTEM_ACCESSTOKEN" \
--data "$PR_COMMENT" \
--verbose

Bash Script Breakdown

COMMENT=$(echo "Test PR comment from Azure DevOps Pipeline")

COMMENT variable will be the value that is going to be added on the Azure Repo Pull Request comment

ADO_API=$(echo "$(System.CollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.Name)/pullRequests/$(System.PullRequest.PullRequestId)/threads?api-version=7.1-preview.1")

ADO_API variable is used to build the concatenated Azure DevOps API endpoint URL. The values System.CollectionUriSystem.TeamProjectBuild.Repository.Name, and System.PullRequest.PullRequestId are predefined Azure DevOps variables as documented here

PR_COMMENT=$(jq --arg comment "$COMMENT" '.comments[0].content = $comment' <<< '{"comments": [{"parentCommentId": 0,"content": "","commentType": 1}],"status": 1}')

PR_COMMENT variable modifies the json structure using jq that the API endpoint will understand. Example output:

{
  "comments": [
    {
      "parentCommentId": 0,
      "content": "Test PR comment from Azure DevOps Pipeline",
      "commentType": 1
    }
  ],
  "status": 1
}
curl --request POST "$ADO_API" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "Authorization: Bearer $SYSTEM_ACCESSTOKEN" \
--data "$PR_COMMENT" \
--verbose

Finally the curl command that will send a POST request to Azure DevOps API with the required $PR_COMMENT, authorisation uses $SYSTEM_ACCESSTOKEN which I will discuss in the below Azure DevOps pipeline

Azure DevOps Pipeline

Here is the example Azure DevOps pipeline:

      - task: Bash@3
        displayName: 'Example PR Comment'
        inputs:
          targetType: 'inline'
          script: |
            COMMENT=$(echo "Test PR comment from Azure DevOps Pipeline")

            ADO_API=$(echo "$(System.CollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.Name)/pullRequests/$(System.PullRequest.PullRequestId)/threads?api-version=7.1-preview.1")

            PR_COMMENT=$(jq --arg comment "$COMMENT" '.comments[0].content = $comment' <<< '{"comments": [{"parentCommentId": 0,"content": "","commentType": 1}],"status": 1}')

            curl --request POST "$ADO_API" \
            --header "Content-Type: application/json" \
            --header "Accept: application/json" \
            --header "Authorization: Bearer $SYSTEM_ACCESSTOKEN" \
            --data "$PR_COMMENT" \
            --verbose
        env:
          SYSTEM_ACCESSTOKEN: $(System.AccessToken)

Notice the environment variable SYSTEM_ACCESSTOKEN highlighted above?

System.AccessToken is a special variable that carries the security token used by the running build.

https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken

Another value to note is: $(System.PullRequest.PullRequestId)

System.PullRequest.PullRequestIdThe ID of the pull request that caused this build. For example: 17. (This variable is initialized only if the build ran because of a Git PR affected by a branch policy).
https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#system-variables-devops-services

Awesome, running this will show the example below. We can see the automated comment from Azure DevOps pipeline

Full Azure DevOps pipeline example:

name: $(BuildDefinitionName)_$(date:yyyyMMdd)$(rev:.r)

trigger: none

pr: none

stages :
  - stage: example
    jobs:
    - job: example
      continueOnError: false
      steps:
      - task: Bash@3
        displayName: 'Example PR Comment'
        inputs:
          targetType: 'inline'
          script: |
            COMMENT=$(echo "Test PR comment from Azure DevOps Pipeline")
            
            ADO_API=$(echo "$(System.CollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.Name)/pullRequests/$(System.PullRequest.PullRequestId)/threads?api-version=7.1-preview.1")

            PR_COMMENT=$(jq --arg comment "$COMMENT" '.comments[0].content = $comment' <<< '{"comments": [{"parentCommentId": 0,"content": "","commentType": 1}],"status": 1}')

            curl --request POST "$ADO_API" \
            --header "Content-Type: application/json" \
            --header "Accept: application/json" \
            --header "Authorization: Bearer $SYSTEM_ACCESSTOKEN" \
            --data "$PR_COMMENT" \
            --verbose
        env:
          SYSTEM_ACCESSTOKEN: $(System.AccessToken)

Azure DevOps Pipeline example can be found in GitHub here

Leave a Reply