'AzureDevops Api call for pull-request doesn't work

I'm referring to this page, to create a pull request during the build process in Azure DevOps via API call. I'm using authorization as Bearer using $(System.AccessToken) instead of PAT credentials. In my previous steps, I used Bearer with $(System.AccessToken) to get the buildNumber via API call which worked seamlessly. However, if I use the same in my below task for the POST mechanism to create a pull request it doesn't work and gives me 400 error code.

Can anyone suggest to me, how can I make this work?

          - task: Bash@3
            inputs:
              targetType: 'inline'
              script: |
                
                
                echo 'Started createing pull-request'
                url="https://dev.azure.com/{Organization name}/{Project name}/_apis/git/repositories/$(Build.Repository.ID)/pullrequests?api-version=6.0"
                echo $url
                ret=$(curl -d '{"sourceRefName": $(BUILD.SOURCEBRANCH), "targetRefName": "refs/heads/devlopment", "title": "test",  "description": "test" }' -X POST -H "Authorization:Bearer $(System.AccessToken)" -H "Content-Type:application/json"  ${url} --write-out "%{http_code}" --output response.json)
                echo $ret
                cat response.json


Solution 1:[1]

The error was in JSON. Fixed and reformatted JSON all working now. This is the task now looks like with some extra addition: Title and Description are parameterized.

- task: Bash@3
            displayName: Creating Pull request 
            inputs:
              targetType: 'inline'
              script: |
                url="$(System.TeamFoundationCollectionUri)/$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.ID)/pullrequests?api-version=6.0"
                ret=$(curl -X POST \
                --silent \
                -H "Authorization:Bearer $(System.AccessToken)"  \
                -H "Content-Type:application/json" \
                ${url} \
                --write-out "%{http_code}" \
                --output response.json \
                -d'{
                  "sourceRefName":"$(BUILD.SOURCEBRANCH)",
                  "targetRefName":"refs/heads/<branch-name>",
                  "title":"$(Title)",
                  "description":"$(Description)"
                  }' )
                if [[ $ret -ne 201 ]];
                then
                  message=$(cat response.json | jq '.message' --raw-output)
                  echo "##vso[task.logissue type=error;] ERROR MESSAGE: ${message}"
                  exit 1
                fi
            continueOnError: false

Solution 2:[2]

Did you check your URL?

In your example:

url="https://dev.azure.com/{Organization name}/{Project name}/_apis/git/REPOSITORIE/$(Build.Repository.ID)/pullrequests?api-version=6.0"

should be

url="https://dev.azure.com/{Organization name}/{Project name}/_apis/git/REPOSITORIES/$(Build.Repository.ID)/pullrequests?api-version=6.0"

Solution 3:[3]

GIT Pullrequest

I was trying to use your code, yet I had a problem
MESSAGE: TF401027: You need the Git 'PullRequestContribute' permission to perform this action. Details: identity 'Build\c5f6a8a4-de0a-4081-bff4-928a38d232c4', scope 'repository'.

The solution comes from https://developercommunity.visualstudio.com/t/tf401027-you-need-the-git-pullrequestcontribute-pe/1441618

Navigate to Project Settings >> Repositories >> select Security tab >> Type Project Collection Build Service (organizationName) in the search box >> check if the Contribute to pull requests permission is set to Allow.
You need to search for this 'Project Collection Build Service' and only then it would appear on your list. There is no add button, only 'search'.
https://dev.azure.com/{owner}/{project}/_settings/repositories?_a=permissions

(example) https://dev.azure.com/jmusz/iospoc/_settings/repositories?_a=permissions enter image description here

My code was based on yours, an example of a plain curl without an error handling is

        - task: Bash@3
          displayName: Creating Pull request 
          inputs:
            targetType: 'inline'
            script: |
              url="$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.ID)/pullrequests?api-version=6.0"
              echo $url

              curl -X POST \
              -v \
              -H "Authorization: Bearer $(System.AccessToken)"  \
              -H "Content-Type: application/json" \
              ${url} \
              --write-out "%{http_code}" \
              --output response.json \
              -d'{
                "sourceRefName": "$(BUILD.SOURCEBRANCH)",
                "targetRefName": "refs/heads/dest",
                "title": "Title",
                "description": "Description"
                }' 
              cat response.json

As you might guess, when I could not find PullRequestContribute (took me a while), I had a 'Plan B' that was to use a Personal Access Token... Turns out, there is a difference with Authorization header:

  • $(System.AccessToken) - you can use it directly, with -H "Authorization: Bearer $(System.AccessToken)"
  • while PAT must be extended with a (username:PAT) that is encoded with BASE64, and Basic not Bearer so you use -H "Authorization: Basic ${b64pat}" \

The code for PAT is:

        - task: Bash@3
          displayName: Creating Pull request 
          inputs:
            targetType: 'inline'
            script: |
              url="$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.ID)/pullrequests?api-version=6.0"
              echo $url

              #works
              b64pat=`echo -n 'jmusz:ljfcuww24sa7ywmgvlvuwrn3v7znmuvbnfymfh4xtnt3tbo6bk' | base64`

              #works (empty username)
              b64pat=`echo -n ':ljfcuww24sa7ywmgvlvuwrn3v7znmuvbnfymfh4xtnt3tbo6bk' | base64`
              #not working (missing `:` ) 
              #b64pat=`echo -n 'ljfcuww24sa7ywmgvlvuwrn3v7znmuvbnfymfh4xtnt3tbo6bk' | base64`


              #echo $b64pat

              #-H "Authorization: Bearer $(System.AccessToken)"  \ #require PullRequestContribute 
              #-H "Authorization: Basic ${b64pat}"  \ # user:PAT | base64

              ret=$(curl -X POST \
              -v \
              -H "Authorization: Basic ${b64pat}"  \
              -H "Content-Type: application/json" \
              ${url} \
              --write-out "%{http_code}" \
              --output response.json \
              -d'{
                "sourceRefName": "$(BUILD.SOURCEBRANCH)",
                "targetRefName": "refs/heads/dest",
                "title": "Title",
                "description": "Description"
                }' )
              if [[ $ret -ne 201 ]];
              then
                message=$(cat response.json | jq '.message' --raw-output)
                echo "##vso[task.logissue type=error;] ERROR MESSAGE: ${message}"
                #cat response.json
                exit 1
              fi

PAT with powershell

        - task: PowerShell@2
          displayName: Creating Pull request  - PS
          inputs:
            targetType: 'inline'
            script: |

              $url = "https://dev.azure.com/jmusz/iospoc/_apis/git/repositories/iospoc/pullrequests?api-version=6.0"
              
              $sourceBranch = "refs/heads/source"
              $targetBranch = "refs/heads/dest"
              $title = "title"
              $description = "description"
              
              
              $MyPat = 'ljfcuww24sa7ywmgvlvuwrn3v7znmuvbnfymfh4xtnt3tbo6bk'
              $B64Pat = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes(":$MyPat")) #Note :  !!!!
              $B64Pat
              $header = @{ Authorization = "Basic $B64Pat" }
              
              $body = @{
                  sourceRefName = "$sourceBranch"
                  targetRefName = "$targetBranch"
                  title         = "$title"
                  description   = "$description"
              }
              
              $jsonBody = ConvertTo-Json $body
              try {
                  $response = Invoke-RestMethod -Uri $url -Method Post -Headers $header -Body $jsonBody -ContentType "application/json;charset=UTF-8"
              }
              catch {
                  Write-Error $_
                  Write-Error $_.Exception.Message
              }
              
              $response

GIT repo clone with Token

I had to pass credentials to repo to a task in pipeline, I decided the best would be to use $(System.AccessToken) (plan B was with PAT). I faced the same problem as above - Bearer $(System.AccessToken) vs Basic ${b64pat}
Please note that username is optional when used with PAT (for git clone) while it is not optional when used for API interaction

  - stage: Lint
    jobs:
      - job: SwiftLint
        steps:

        - bash: |
            b64pat=`echo -n jmusz:ljfcuww24sa7ywmgvlvuwrn3v7znmuvbnfymfh4xtnt3tbo6bk |base64`

            echo 1 not working
            git clone $(System.TeamFoundationCollectionUri)$(System.TeamProject)/_git/$(Build.Repository.ID) ||true
            #rm -rf libraries ||true

            echo 2 not working
            #not working
            git -c http.extraHeader="Authorization: Basic $(System.AccessToken)" clone $(System.TeamFoundationCollectionUri)$(System.TeamProject)/_git/$(Build.Repository.Name) ||true
            rm -rf iospoc ||true
            
            echo 2.1 working
            #working
            git -c http.extraHeader="Authorization: Bearer $(System.AccessToken)" clone $(System.TeamFoundationCollectionUri)$(System.TeamProject)/_git/$(Build.Repository.Name) ||true
            rm -rf iospoc ||true

            echo 3 working
            git -c http.extraHeader="Authorization: Basic ${b64pat}" clone $(System.TeamFoundationCollectionUri)$(System.TeamProject)/_git/$(Build.Repository.Name) ||true
            #git -c http.extraHeader="Authorization: Basic $(System.AccessToken)" clone https://dev.azure.com/jmusz/iospoc/_git/libraries/  ||true
            rm -rf libraries ||true
          
            echo 4 not working
            git clone "https://jmusz:$(System.AccessToken)@dev.azure.com/jmusz/iospoc/_git/libraries" ||true
            rm -rf libraries ||true

            echo 4.01 not working
            git clone "https://$(System.AccessToken)@dev.azure.com/jmusz/iospoc/_git/libraries" ||true
            rm -rf libraries ||true

            echo 4.1 working
            git clone "https://jmusz:[email protected]/jmusz/iospoc/_git/libraries" ||true
            rm -rf libraries ||true
            echo 4.2 working
            git clone "https://[email protected]/jmusz/iospoc/_git/libraries" ||true
            rm -rf libraries ||true
           
            #not working
            echo 5 not working
            git clone https://${b64pat}@dev.azure.com/jmusz/iospoc/_git/libraries ||true  
            rm -rf libraries ||true
          
            #not working
            echo 6 not working
            git clone https://$(System.AccessToken)@dev.azure.com/jmusz/iospoc/_git/libraries ||true  
            rm -rf libraries ||true
           
            #working
            echo 7 working
            git config --global --add http.https://dev.azure.com/jmusz/iospoc/_git/libraries.extraHeader "AUTHORIZATION: Basic ${b64pat}"
            git clone https://dev.azure.com/jmusz/iospoc/_git/libraries/  ||true
            rm -rf libraries ||true

            #not working?
            echo 8 not working
            git config --global --add http.https://dev.azure.com/jmusz/iospoc/_git/libraries.extraHeader "AUTHORIZATION: Bearer $(System.AccessToken)"
            git clone https://dev.azure.com/jmusz/iospoc/_git/libraries/  ||true
            rm -rf libraries ||true

Interesting that 2.1 works while 8 does not

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Shamrai Aleksander
Solution 3 sirkubax