Github self-service with Terraform
It is late Friday evening; I am sitting in Schiphol Airport and my brain is still processing information from my visit at our Amsterdam office. One thing I noticed, was the similarity between our teams use and use cases of Terraform. I am thinking to myself, this specific use case might inspire you and why not share it? Before this turns into one of those food blogs, where you get the authors life story before getting to the recipe let’s move on.
At BESTSELLER, we are using Github as our main version control system. For us it is a great collaboration tool making it easy to collaborate between teams and projects. In our Github we structure repository permissions and ownership by allocating people and repositories to teams. But to make the collaboration as efficient and easy as possible we need to keep of track of which people belong to which teams and which repositories should be assigned to a specific team…. To be honest this was a horrible task for us in "Engineering" (which is our enabling(something else) team) .
That is why we wanted to enable our fellow developers to maintain this information by themselves. That empowers the teams to self manage, and saves us from repetitive, time wasting work.
As we already manage a bunch of our stuff with Terraform, we thought that it would be pretty neat to keep going in that direction and use Terraform for our small self-service. Aaaand "Github-teams" were born!
We had a few requirements that needed to be fulfilled:
- As we are using SSO with Azure AD, we needed to assign a Github license to specific Azure users.
- Assigning users to a github team (which in reality is mapping an Azure AD Group to a Github team).
- Create new teams or modify existing teams, based on Azure groups.
To visualize it, the flow in its simplest form looks like: As seen above, we use our Azure AD as the source of truth and then map the groups to Github.
Structure
Now that we have the concepts and requirements nailed, let's take a look at how we actually do it. Our Terraform consists of 3 base modules and a folder with all the teams.
1|-- github-teams/ # one file for each team to contributing a bit easier.
2 |-- team-a.tf
3 |-- team-b.tf
4|-- modules/
5 |-- azure # base module maintaining groups and members in Azure AD
6 |-- github # base module maintaining groups and group mappings between Github and Azure AD
7 |-- team # base module combining Azure and Github based on the input from the github-teams folder.
8github_members.tf # All github members. (if you are not on a team you can still be a part of the organisation)
9main.tf
If we start with the base modules, we have created them generic enough that they can be called for each team without having to write the same code multiple times.
1### AZURE BASE MODULE ###
2# taking care of groups and users in azure
3
4data "azuread_users" "users" {
5 user_principal_names = var.members
6}
7
8# manage azure group and members
9resource "azuread_group" "group" {
10 display_name = var.name
11 description = var.description == "" ? "Grants access to the GitHub Team '${var.team_name}'" : var.description
12 members = data.azuread_users.users.object_ids
13 security_enabled = true
14}
1### GITHUB BASE MODULE ###
2
3# create/manage a github team
4resource "github_team" "team" {
5 name = var.name
6 description = var.description
7 privacy = "closed"
8 parent_team_id = var.parent != null ? var.parent : null
9 create_default_maintainer = var.create_default_maintainer
10}
11
12# map with Azure AD group
13resource "github_team_sync_group_mapping" "mapping" {
14 team_slug = github_team.team.slug
15
16 group {
17 group_id = var.group_id
18 group_name = var.group_name
19 group_description = var.description
20 }
21}
1### TEAM BASE MODULE ###
2# we invoke the Azure and Github module for each team and make sure they map 1-1.
3
4module "azure" {
5 source = "../azure"
6
7 name = local.azure_group_name
8 team_name = var.name
9 members = var.members
10}
11
12module "github" {
13 source = "../github"
14
15 name = var.name
16 description = var.description
17 group_id = module.azure.group_id
18 group_name = module.azure.group_name
19 parent = var.parent != null ? var.parent : null
20 create_default_maintainer = var.create_default_maintainer
21}
Beside the base modules the most important part is the github-teams
folder where we have a file for each team. The only information needed is the Team Name and the members. Easy right?
1# example for "Team A" in the github-teams folder.
2
3module "team_a" {
4 source = "../modules/team"
5
6 name = "Team A"
7 members = [
8 "first.member@somedomain.com",
9 "second.member@somedomain.com",
10 ]
11}
Using the self-service
So if you are a developer in BESTSELLER and you need to add a new colleague, it is as easy as making a pull request with two changes:
- Adding the person to the members file
- Assigning the person to a team.
Wait for review and approval and the CI will take care of the rest!
About the author
Peter Brøndum
My name is Peter Brøndum, I work as a Tech Lead and Scrum Master in a platform engineering team at BESTSELLER. Our main priority is building a development highway, with paved roads, lights and signs, so our colleagues can deliver value even faster. Besides working at BESTSELLER, I — amongst other things, am automating my own home, and yes, that is, of course, running on Kubernetes as well.