はじめに
GitHubのレポもterraformで管理しておくと便利なことがいくつかあります。
- 権限変更履歴が把握しやすくなる
- Admin権限でないとみれない設定をコード上で誰でも確認できるようになる
- レポジトリの設定の統一 (Branch のRuleset (branch protection) 、マージ方法などの設定)
- GitHub Template Repositoryを使ってレポジトリを作成してもレポジトリの設定まではコピーされない
3年前に似たような記事を書いていましたが、2025年の時点でやっている管理を書いておきます。
構成
- backend: GCS bucket (S3などでも可)
- ci/cd: GitHub Actions (Terraform Cloudでもよいが最近ほとんどGitHub Actionsに統一してるのでGitHub Actionsにしました)
設定手順
1. GitHub App の作成
-
GitHub設定にアクセス
https://github.com/settings/apps (個人アカウント) または https://github.com/organizations/YOUR_ORG/settings/apps (組織)
-
新しいGitHub Appを作成
- New GitHub App をクリック
- 基本情報を入力:
-
App name:
GH Resource Manager
-
Description:
Manage GitHub resources using Terraform
- Homepage URL: あなたのリポジトリURL
-
App name:
-
権限設定
Repository permissions で以下を設定:Administration: Read & write # リポジトリ設定管理(vulnerability alerts含む) Contents: Read & write # ファイル操作 Metadata: Read # 基本情報読み取り Pull requests: Read & write # PR管理 Actions: Read # GitHub Actionsアクセス(必要に応じて)
-
Where can this GitHub App be installed?
- "Only on this account" を選択(個人の場合)
- または適切な組織を選択
-
Create GitHub App をクリック
2. GitHub App のインストール
- App設定ページで "Install App" タブをクリック
- 対象のアカウント/組織で "Install" をクリック
-
Repository access で以下のいずれかを選択:
- "All repositories" (管理するRepositoryの権限を取得する必要がある)
3. 認証情報の取得
GitHub App設定ページで以下を取得:
- App ID: General タブの "App ID" をコピー
-
Private Key:
- General タブの "Private keys" セクション
- "Generate a private key" をクリック
- ダウンロードされた
.pem
ファイルの内容をコピー
4. GitHub Secrets の設定
リポジトリの Settings > Secrets and variables > Actions で以下を追加:
GH_APP_APP_ID=12345
GH_APP_PRIVATE_KEY=-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
注意: 現在の実装では installation-id
は不要です(自動検出されます)。
5. Terraform設定
現在の provider.tf
は既に最適化されています:
terraform {
required_providers {
github = {
source = "integrations/github"
version = "~> 6.0"
}
}
}
# Configure the GitHub Provider
# Local: gh auth login
# CI: GITHUB_TOKEN env var
provider "github" {
owner = "nakamasato"
}
この設定により:
- ローカル: GitHub CLIの認証情報を自動使用
-
CI:
GITHUB_TOKEN
環境変数から自動読み取り
terraform.tf
にバックエンドの設定を記載します。
terraform {
backend "gcs" {
bucket = "yourproject-bucket-terraform"
prefix = "github"
}
}
あとは、Terraformで管理したいチーム、メンバー、レポジトリ、GitHub Actionsシークレット Branch Protectionなどを設定します。
- github_team
- github_membership
- github_team_members
- github_team_membership
- github_repository_collaborators
- github_repository_collaborator
- github_repository
- github_actions_secret
- github_branch_protection_v3
- ...
例. https://github.com/nakamasato/mysql-operator repoの管理
resource "github_repository" "mysql-operator" {
name = "mysql-operator"
description = "Manage MySQL users, databases, schemas, etc."
allow_merge_commit = false
allow_rebase_merge = false
allow_update_branch = true
has_downloads = true
has_issues = true
has_projects = false
has_wiki = false
vulnerability_alerts = true
delete_branch_on_merge = true
squash_merge_commit_title = "PR_TITLE"
squash_merge_commit_message = "BLANK"
homepage_url = "https://nakamasato.github.io/mysql-operator/"
topics = [
"kubernetes",
"kubernetes-operator",
"managed-by-terraform",
]
pages {
cname = ""
source {
branch = "gh-pages"
path = "/"
}
}
visibility = "public"
}
6. ローカル環境の設定
GitHub CLIでログイン:
gh auth login
これで Terraform が自動的に GitHub CLI の認証情報を使用します。
7. GitHub Actionsワークフローの作成
以下の例はgithub
以下にterraformコードがあるケースを想定しています。
- PRでplan + tfcmtを使ってコメント
- mainにpushでapply
name: github
on:
push:
branches: [main]
paths:
- .terraform-version
- '.github/workflows/github.yml'
- 'github/**'
- '!github/**md'
pull_request:
branches: [main]
paths:
- .terraform-version
- '.github/workflows/github.yml'
- 'github/**'
- '!github/**md'
jobs:
terraform:
permissions:
contents: read
pull-requests: write # PR にコメントを投稿するのに必要
issues: write # tfcmt が一番最初にRepository labelを追加するのに必要
id-token: write # authenticate gcpでworkload identityを使用するときに必要
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.GH_APP_APP_ID }} # Repository SecretにGitHub Appの App IDを設定しておきます
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} # Repository SecretにGitHub AppのPrivate keyを設定しておきます
owner: ${{ github.repository_owner }}
# backendにgcsを使ってるために必要 (s3を使っている場合はawsのauthに置き換え)
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/<project_number>/locations/global/workloadIdentityPools/github/providers/github-provider # 自分のWorkload Identity Providerを書いてください
service_account: github-actions@<project>.iam.gserviceaccount.com # 自分のService Accountを書いてください (backendのGCS bucket への書き込み権限を付与しておく必要があります)
create_credentials_file: 'true'
- name: set-tf-version
id: set-tf-version
run: |
echo "terraform_version=$(cat .terraform-version)" >> $GITHUB_OUTPUT
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ steps.set-tf-version.outputs.terraform_version }}
- name: Terraform Init
working-directory: github
run: terraform init
- name: Terraform Plan
if: github.event_name != 'push' || github.ref_name != 'main'
env:
# https://github.com/suzuki-shunsuke/tfcmt/blob/6cb66ca833829e713563b928f0b99f1401dd6677/pkg/notifier/github/client.go#L71-L79
TFCMT_GITHUB_TOKEN: ${{ secrets.github_token }} # tfcmt用のgithub token (権限は上のpermissionsで設定されている)
GITHUB_TOKEN: ${{ steps.github-app-token.outputs.token }} # Terraform github provider 用のGitHub token (GitHub appで権限は設定済み)
run: tfcmt -var "target:${{ inputs.project }}" plan -patch -- terraform plan -no-color
- name: Terraform Apply
if: github.event_name == 'push' && github.ref_name == 'main'
env:
# https://github.com/suzuki-shunsuke/tfcmt/blob/6cb66ca833829e713563b928f0b99f1401dd6677/pkg/notifier/github/client.go#L71-L79
TFCMT_GITHUB_TOKEN: ${{ secrets.github_token }} # tfcmt用のgithub token (権限は上のpermissionsで設定されている)
GITHUB_TOKEN: ${{ steps.github-app-token.outputs.token }} # Terraform github provider 用のGitHub token (GitHub appで権限は設定済み)
run: tfcmt -var "target:${{ inputs.project }}" apply -- terraform apply -no-color -auto-approve -input=false
GitHub Tokenは二種類あるのに注意
- Terraform のGitHub providerの認証に使うトークン
-
GITHUB_TOKEN
環境変数を使って GitHub Appから取得したトークンを渡す
-
- tfcmtがterraform plan/applyの結果を PRにコメントとして残す/PRに変更に対応するlabelをつける
-
TFCMT_GITHUB_TOKEN
環境変数を使って、workflowで生成されるsecrets.github_token
で取得できるトークンを渡す
-
githubのprovider設定でimplicitに認証して、ローカルからplanを実行するときもgh コマンドさえ設定してあれば簡単に実行ができるので便利にしているので、CIからはGITHUB_TOKEN
環境変数を通して認証することでCI/Localでのバランスを取れた設定しにしています。
provider "github" {
owner = "nakamasato"
}
単純にGITHUB_TOKEN
にGitHub appから取得したトークンを渡すと、Repositoryのlabelの作成権限がないので、tfcmt のlabel作成に失敗してしまいます。
テスト
ローカルテスト
cd github
gh auth login # 必要に応じて
terraform init
terraform plan
GitHub Actionsテスト
- 変更をコミット・プッシュ
- Actions タブでワークフロー実行を確認
- ログでエラーがないか確認
こんな感じのコメントが来ていればOk
トラブルシューティング
-
GitHub App権限エラー
Error: GET https://api.github.com/repos/owner/repo: 404 Not Found
- App の Repository permissions を確認
- Installation が正しいリポジトリに設定されているか確認
-
Vulnerability alerts権限エラー
Error: 403 Resource not accessible by integration []
- GitHub App の Administration 権限が Read & write に設定されているか確認
- Installation を更新して新しい権限を承認
- すべてのリポジトリに対してインストールされていることを確認
-
Installation ID不明
# 全てのinstallationを確認 gh api /user/installations
-
Private Key形式エラー
- PEMファイルの内容をそのままコピー
- 改行文字も含めて正確にコピー
Tips: Reusable GitHub ActionsWorkflow
backend をGCS bucketで管理する場合は、Reusable workflowを作って簡単に設定できるようにしています。
name: terraform-github
on:
workflow_call:
inputs:
working_directory:
type: string
required: false
default: "."
project:
type: string
required: false
default: "github"
# GCP config: currently only support GCS backend
workload_identity_provider:
type: string
required: false
service_account:
type: string
required: false
secrets:
# GitHub App
gh_app_id:
required: true
gh_private_key:
required: true
concurrency:
group: terraform-github-${{ inputs.project }}
cancel-in-progress: false
jobs:
terraform:
permissions:
issues: write # allow to create repo label
contents: read
id-token: write
pull-requests: write
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working_directory }}
steps:
- uses: actions/checkout@v4
- name: Generate GitHub App Token
id: github-app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.gh_app_id }}
private-key: ${{ secrets.gh_private_key }}
owner: ${{ github.repository_owner }}
- name: install aqua
uses: aquaproj/aqua-installer@v4.0.0
with:
aqua_version: v2.51.2
- name: install tfcmt via aqua
run: |
if [ -f aqua.yaml ];then
aqua i
else
aqua g -i suzuki-shunsuke/tfcmt
fi
# For GCS bucket backend
- id: 'auth'
name: 'Authenticate to Google Cloud'
uses: google-github-actions/auth@v2
with:
create_credentials_file: 'true'
workload_identity_provider: ${{ inputs.workload_identity_provider }}
service_account: ${{ inputs.service_account }}
- name: set-tf-version
id: set-tf-version
working-directory: .
run: |
echo "terraform_version=$(cat .terraform-version)" >> $GITHUB_OUTPUT
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ steps.set-tf-version.outputs.terraform_version }}
- name: Terraform fmt
id: fmt
run: terraform fmt -check -recursive
- name: Terraform Init
id: init
run: terraform init
- name: Terraform Validate
id: validate
run: terraform validate -no-color
- name: Terraform Plan
if: github.event_name != 'push' || github.ref_name != 'main'
env:
# https://github.com/suzuki-shunsuke/tfcmt/blob/6cb66ca833829e713563b928f0b99f1401dd6677/pkg/notifier/github/client.go#L71-L79
TFCMT_GITHUB_TOKEN: ${{ secrets.github_token }} # For tfcmt (permissions are set above)
GITHUB_TOKEN: ${{ steps.github-app-token.outputs.token }} # For github provider (permissions are set for the GitHub app)
run: tfcmt -var "target:${{ inputs.project }}" plan -patch -- terraform plan -no-color
- name: Terraform Apply
if: github.event_name == 'push' && github.ref_name == 'main'
env:
# https://github.com/suzuki-shunsuke/tfcmt/blob/6cb66ca833829e713563b928f0b99f1401dd6677/pkg/notifier/github/client.go#L71-L79
TFCMT_GITHUB_TOKEN: ${{ secrets.github_token }} # For tfcmt (permissions are set above)
GITHUB_TOKEN: ${{ steps.github-app-token.outputs.token }} # For github provider (permissions are set for the GitHub app)
run: tfcmt -var "target:${{ inputs.project }}" apply -- terraform apply -no-color -auto-approve -input=false
これを使うことで、設定がとても簡単になります。
実際に使う場合は以下のように設定するだけになります。
name: github
on:
push:
branches: [main]
paths:
- .terraform-version
- '.github/workflows/github.yml'
- 'github/**'
- '!github/**md'
pull_request:
branches: [main]
paths:
- .terraform-version
- '.github/workflows/github.yml'
- 'github/**'
- '!github/**md'
jobs:
terraform:
uses: nakamasato/github-actions/.github/workflows/reusable-terraform-github.yml@1.11.2
with:
working_directory: github
workload_identity_provider: projects/<your project number>/locations/global/workloadIdentityPools/github/providers/github-provider
service_account: github-actions@<your project>.iam.gserviceaccount.com
secrets:
gh_app_id: ${{ secrets.GH_APP_APP_ID }}
gh_private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}