こんにちは、株式会社HRBrainのプラットフォームチームに所属している永山(@ikorihn)です。
GitHub Organization配下のチームや所属メンバーの管理をTerraformに移行しましたので、この記事ではその時の対応内容をお伝えします。
本記事はHRBrain Advent Calendar 2024の13日目の記事です。
はじめに
所属メンバーやチームの数が増えてくるに従い、以下のような課題が発生していました。
- だれがどのチームに所属しているか把握しづらい
- 異動の際に管理者が手動でチームメンバーを変更していて手間な上、履歴が残らない
- 本名とGitHubアカウントとの突合がしづらい
これらの問題を解決するため、メンバーの一覧や各チームの定義をTerraformで管理することにしました。
GitHub Provider
公式に提供されているGitHub Providerを使用します。
providerの設定
GitHubの操作に必要なtokenは、GitHub Actions内で生成したアクセストークンを渡すことにします(後述)
terraform {
required_providers {
github = {
source = "integrations/github"
version = "~> 6.0"
}
}
}
provider "github" {
owner = "<organization name>"
token = var.github_app_token
}
variable "github_app_token" {
type = string
}
所属メンバー
メンバーの管理にはgithub_membershipリソースを使用します。
for_each
で定義しました。
locals {
hrbrain_members = {
full_name1 = { username = "<github user 1>", role = "admin" },
full_name2 = { username = "<github user 2>", role = "member" },
full_name3 = { username = "<github user 3>", role = "member" },
}
}
resource "github_membership" "members" {
for_each = local.hrbrain_members
username = each.value.username
role = each.value.role
}
メンバーの一覧は、組織ownerにCSVでダウンロードしてもらったものを加工して作成しました。
login name saml_name_id
user1 User Name1 full_name1@ドメイン
user2 User Name2 full_name2@ドメイン
チーム
チーム自体の定義にはgithub_teamリソース、チームメンバーの管理にはgithub_team_membersリソースを使用します。
resource "github_team" "team1" {
name = "team1"
parent_team_id = github_team.developer.id # 子チームとして定義する場合はこれを指定する
privacy = "closed"
}
resource "github_team_members" "team1" {
team_id = github_team.team1.id
members {
username = github_membership.members["full_name2"].username
role = "maintainer"
}
members {
username = github_membership.members["full_name3"].username
# roleを指定しない場合デフォルトでmemberとなる
}
}
このファイルをチームごとに作成しています。
ただ、既存のチームの設定すべてを手で書いていくのが大変でしたので、後述のimport blockと terraform plan -generate-config-out
を用いて作成しました。
既存の設定をTerraform管理下にimportする
すでに作成済みのチームやメンバーをTerraformの管理化に追加するためにimportしていきます。
Terraform v1.5.0で追加されたImport blockを使用して移行しました。
importブロックのidにはインポート元、toにはインポート先リソースの識別子を指定します。
idのフォーマットは、resourceドキュメントのImportを見るとわかります。
たとえばgithub_membershipであれば <organization>:<username>
を指定します。
メンバーのimportブロックを作る
id
に <organization>:<username>
、to
に github_membership
のリソース識別子を指定します。
こちらも for_each
で作成できます。
import {
for_each = local.hrbrain_members
id = "<organization>:${each.value.username}"
to = github_membership.members[each.key]
}
チームのimportブロックを作る
GitHubのAPIでチーム一覧をJSONで取得したあと、github_team およびgithub_team_members のimportブロックに加工します。
$ gh api --paginate /orgs/<organization>/teams > teams.json
$ cat teams.json | jq -cr '.[] | "import {| id = " + (.id | tostring) + "| to = github_team." + .slug + "|}"' | sed 's/|/\n/g' > import_teams.tf
$ cat teams.json | jq -cr '.[] | "import {| id = " + (.id | tostring) + "| to = github_team_members." + .slug + "|}"' | sed 's/|/\n/g' | >> import_team_members.tf
import {
id = 1234567
to = github_team.team1
}
import {
id = 1234568
to = github_team.team2
}
import {
id = 1234567
to = github_team_members.team1
}
import {
id = 1234568
to = github_team_members.team2
}
generate-config-out オプションによって、importブロックからHCLを作成できるようになっています。
$ terraform plan -generate-config-out=generated.tf
実行すると次のようなファイルが生成されるので、これを編集して各チームのtfファイルを作成しました。
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
# __generated__ by Terraform from "1234567"
resource "github_team" "team1" {
name = "team1"
privacy = "closed"
}
# __generated__ by Terraform from "1234568"
resource "github_team" "team2" {
name = "team2"
privacy = "closed"
}
# __generated__ by Terraform from "1234567"
resource "github_team_members" "team1" {
team_id = jsonencode("1234567")
members {
username = "user1"
role = "maintainer"
}
}
# __generated__ by Terraform from "1234568"
resource "github_team_members" "team2" {
team_id = jsonencode("1234568")
members {
username = "user1"
role = "maintainer"
}
members {
username = "user2"
role = "member"
}
}
あとは terraform apply
を実行すると、リソースがimportされます。
$ terraform apply
Terraform will perform the following actions:
# github_team.team1 will be imported
resource "github_team" "team1" {
....
Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.
ここまで来たらimportブロックは削除してOKです。
GitHub ActionsでTerraformを実行する
プルリクエストベースで terraform apply
を実行するために、GitHub Actionsを設定します。
GitHubのアクセストークンは、job内でCreate GitHub App Tokenを使って生成します。
まずはGitHub Appを作成します。
権限はOrganization permissions for "Members"をつけます。
作成したGitHub AppのIDをリポジトリのvariableに、private keyをsecretに設定します。
name: Update GitHub member/team
on:
push:
branches:
- main
jobs:
apply:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9
- run: terraform init -input=false
- name: Generate a GitHub token
id: generate-github-token
uses: actions/create-github-app-token@v1
with:
# GitHub Appsの情報を設定する
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- run: terraform apply -input=false -auto-approve -lock=false -no-color
env:
TF_VAR_github_app_token: ${{ steps.generate-github-token.outputs.token }}
まとめ
これまでGitHubのチームやメンバーをWeb画面上から手動で設定していたのを、Terraform管理下に移行する手順を紹介しました。
Terraform化したことにより、次のようなメリットがありました。
- 本名とGitHubアカウントとの突き合わせが容易になった
- だれがどのチームに所属しているか一覧で確認しやすくなった
- 退職時や異動時に本名でgrepできるようになったので、漏れが出にくくなった
- プルリクベースで設定変更が行えるようになった
ここまで読んでいただきありがとうございました!参考になれば幸いです。