Azure Pipelines Shared Service Connections to GitHub
This is a story about yaml based Azure Pipelines and GitHub Repos that call yaml templates also in GitHub.
First the players.
- Multiple Team Projects under one Organization in Azure DevOps.
- Each Team Project has multiple yaml pipelines.
- All the Repos are in GitHub.
- They all make calls to yaml templates in one main template repo in GitHub.
So here’s the story: We have started moving all the Repos to GitHub, but not the Pipelines, at least not yet. Therefore we installed the amazing Azure Pipelines app in GitHub so our pipelines can use the GitHub Repos we have migrated.
Each pipeline has .yml files that has a reference to one repo that contains all the templates for build and release. Like this:
resources:
repositories:
- repository: templates
type: github
name: MyGitHubOrg/MyTemplatesRepo
ref: refs/heads/master
endpoint: MyGitHubOrgstages:
- template: build.yml@templates
parameters:
...
The main character in this story is the endpoint MyGitHubOrg. This is the name of a service connection created by the Azure Pipelines GitHub App mentioned above.
When you install the Azure Pipelines app it will create the Service Connection for you. In whatever team project you supply during installation.
Once it’s installed and working for one Team Project in Azure DevOps you can share it with other projects so they can also use it. To do this go to that Service Connection in Azure DevOps and select Security. If you are not sure where this is it’s under Project Settings — Service Connections in Azure DevOps.
At the bottom of the Service Connection Security page you can set up project permissions so all the other Team projects in your Org can use the connection as well. Simply click the plus sign to share it with another Team Project. Easy.
Here’s the problem: Each shared service connection created has a different name. <NameofServiceConnection>-<NameofProjectSharedWith>
This means the pipeline.yml files in all the repos in each team project have a different endpoint in the template repository resource. Something like this.
resources:
repositories:
- repository: templates
type: github
name: MyGitHubOrg/MyTemplatesRepo
ref: refs/heads/master
endpoint: MyGitHubOrg-MyTeamProject
That sucks, they are all referencing the same templates repo it would be much better if this resource reference was the same in every repo using the templates.
To solve this issue I wrote a PowerShell script to share the service connection and use the same name in every team project.
Some things you need for this script.
- You’ll need a Personal Access Token (PAT) for Azure DevOps, stored in an environment variable called $env:ADO_PAT
2. The id of the Service Connection we want to share. You can easily get this by opening the service connection from the project settings. The Id is right there in the URL. Blacked out in the diagram for my protection.
3. This script uses a handy little .json file to use as a template for the API Body. Let’s call it sharedconnection.json. It should look exactly like this.
[{
"projectReference": {
"id": "_id_",
"name": "_name_" },
"name": "Parts Unlimited",
"description":"Shared github service connection"
}]
Now you have everything you need to call the script below.
$ado_project = "TheNameOfTheAzDOTeamProjectIWantToShareWith"
$ado_connectionId = “TheConnectionIdIGotFromTheURL”
$ado_org = 'https://dev.azure.com/MyAzDOOrg'
# Login to Azure DevOps
echo $env:ADO_PAT | az devops login --org $ado_org
# Get the project ID
$project = az devops project show --org $ado_org --project
$ado_project | ConvertFrom-Json
$projectId = $project.id
$projectName = $project.name# Use the json template we created as the body of the API call
$body = Get-Content -path "./sharedconnection.json" -Raw
# Inject the project id and project name of the
# service connection to share into the body
$body = $body.Replace("_id_", $projectId)
$body = $body.Replace("_name_", $projectName)
# Call the Azure DevOps Rest API to share the service connection
$params = @{'Uri' =
('{0}/_apis/serviceendpoint/endpoints/{1}?api-version=6.0-
preview.4' -f $ado_org, $ado_connectionId)
'Headers' = @{'Authorization' = 'Basic ' +
[Convert]::ToBase64String
([Text.Encoding]::ASCII.GetBytes
(":$($env:ADO_PAT)"))}
'Method' = 'PATCH'
'ContentType' = 'application/json'
'Body' = ($body)}
Invoke-RestMethod @params
Now you have a Shared Service Connection with the same name as the original.