
Terraform+CSV+GitHub ActionsでGitHub Organizationのメンバー管理をやってみた-③仕組み編
こんにちは!クラウド事業本部の吉田です。
皆さん、GitHub Organization利用していますか?
Terraform+CSV+GitHub Actionsを利用して、GitHub Organizationのメンバー管理をする方法とその初期構築を紹介させていただきました。
当記事は、その仕組みの詳細を解説した記事となります。
AWS環境、Terraform、GitHub Actionsに分けて解説します。
記事リンク
- Terraform+CSV+GitHub ActionsでGitHub Organizationのメンバー管理をやってみた-①概要・利用方法編
- Terraform+CSV+GitHub ActionsでGitHub Organizationのメンバー管理をやってみた-②初期構築編
- Terraform+CSV+GitHub ActionsでGitHub Organizationのメンバー管理をやってみた-③仕組み編 ←イマココ
サンプルコード
サンプルコードは、下記のリポジトリで公開しております。
サンプルコードのディレクトリ/ファイル構成
.
├── .github/
│ └── workflows/ #GitHub Actionsのワークフローファイルの格納フォルダ
│ ├── apply.yml # Pull Requestマージ時に実行されるterraform apply用ワークフローファイル
│ └── plan.yml # Pull Request時に実行されるterraform plan用ワークフローファイル
├── shell/
│ └── script.sh # GitHub Appsトークン生成用シェルスクリプト
├── terraform/ #tfファイル格納用フォルダ
│ ├── module/
│ │ └── terraform-github-organization/ # Organizationのメンバー管理用モジュールフォルダ
│ │ ├── main.tf # モジュールのメインtfファイル
│ │ └── variables.tf # モジュールの変数用tfファイル
│ ├── .terraform.lock.hcl # terraform init時に自動生成されるプロバイダロックファイル
│ ├── organization.tf # モジュールに渡す引数を定義するtfファイル。チーム追加時などに更新するファイル
│ ├── README.md # organization.tfの内容を説明したREADMEファイル
│ └── versions.tf # terraform version、プロパイダー、バックエンドを指定するファイル
├── users/ #メンバー管理用のCSVファイルを格納するフォルダ
│ └── *.csv
├── .gitignore
└── README.md
AWS環境
初期構築の記事のおさらいです。
Terraformにはtfstateファイルというリソースの状態を管理するファイルがあり、そのリモート管理する場所としてS3を利用します。
GitHubとAWSへのアクセス認証は、OIDCによる一時的なクレデンシャルを利用します。
アクセスキー・シークレットキーのような永続的なクレデンシャルを渡すことなく、セキュアにGitHub Actionsを実行することができます。
細かな設定内容は初期構築の記事を参照してください。
Terraform
最初の記事のおさらいです。
GitHubリソースを管理するためのGitHub Providerがあります。
このGitHub Providerを利用して、Organization・チームのメンバー管理を行います。
Organization・チームのメンバー情報はCSV、チーム情報はtfファイルで管理します。
Terraformのチュートリアルと同様にメンバー情報はCSVにまとめることでtfファイルの肥大化を防ぎます。
チーム情報に関しては、チュートリアルと異なりtfファイルで管理しています。
organization.tf・version.tf
organization.tf
locals {
owners = csvdecode(file("../users/owners.csv"))
members = csvdecode(file("../users/members.csv"))
test-team = csvdecode(file("../users/test-team.csv"))
exsit-team = csvdecode(file("../users/exsit-team.csv"))
}
module "organization" {
source = "./module/terraform-github-organization"
name = "example"
owners = local.owners[*].username
members = local.members[*].username
existing_teams = ["exist-team"]
teams = [
{
name = "Test Team"
members = local.test-team[*].username
},
{
name = "exist-team"
members = local.exsit-team[*].username
}
]
}
organization.tfでは、モジュールに渡すOrganization・チームのメンバー情報の定義とモジュールの呼び出しを行っています。
チーム追加時などにこのorganization.tfを更新する必要があります。
詳細な利用方法は、organization.tfのREADMEと最初の記事を参照してください。
ポイントは、既存チームと新規チームを区別して管理できる点です。
teamsブロックで設定されたチームは基本的にTerraformによって新規作成されます。
existing_teamsパラメータで既存のTeamを指定することで、そのチームは新規作成せずメンバー管理のみを行う例外処理をモジュール側で設定しております。
既存のチームに関してはslug形式で指定する必要があります。
また、既存チームのメンバー管理ですが、既存のメンバーの情報をCSVに記載しますとそのメンバーはTerraform管理にすることができます。
versions.tf
# see https://www.terraform.io/docs/configuration/terraform.html
terraform {
required_version = "~> 1.10.5"
required_providers {
github = {
source = "hashicorp/github"
version = "~> 6.5.0"
}
}
backend "s3" {
bucket = "" #S3バケット名を指定
key = "" #tfstateファイル用のKeyを指定。特に命名規則がなければ、「{Org名}/{リポジトリ名}/tfstate」で指定することを推奨
region = "ap-northeast-1"
acl = "bucket-owner-full-control"
}
}
versions.tfは以下の設定を定義しております。
- Terraformバージョン
- GitHub providerバージョン
- backend(tfstateファイル保管場所)
backendに関してはAWS上で構築したS3バケットとtfstateファイル名を記載してください。
モジュール
モジュールでは、Organization全体のメンバー管理とチーム管理の具体的な処理を実装しています。
variables.tfで変数とローカル値の定義を行い、main.tfでGitHubリソースの実際の操作を行う形で分けています。
variables.tf
variable "name" {
type = string
description = "The name of the organization."
}
variable "owners" {
type = set(string)
default = []
description = "List of owners."
}
variable "blocked_users" {
type = set(string)
default = []
description = "List of blocked users."
}
variable "members" {
type = set(string)
default = []
description = "List of members."
}
variable "teams" {
type = any
default = []
description = "List of teams. This should be `teams` object."
}
variable "existing_teams" {
type = set(string)
default = []
description = "List of existing team names"
}
locals {
memberships = merge(
{ for u in var.owners : u => "admin" },
{ for u in var.members : u => "member" },
{
for u in setunion(
flatten(local.teams[*].maintainers),
flatten(local.teams[*].members)
) : u => "member" if ! contains(var.owners, u)
}
)
# チーム設定の基本形
teams = [
for t in var.teams : merge({
name = ""
description = ""
visible = true
maintainers = []
members = []
}, t)
]
# 既存のTeamと新規のTeamを分離
new_teams = {
for team in local.teams : team.name => team
if !contains(var.existing_teams, team.name)
}
teams_maintainers = flatten([
for t in local.teams : [
for u in t.maintainers : {
team_name = t.name
username = u
role = "maintainer"
}
]
])
teams_members = flatten([
for t in local.teams : [
for u in t.members : {
team_name = t.name
username = u
role = "member"
}
]
])
team_memberships = {
for m in concat(local.teams_maintainers, local.teams_members) :
"${m.team_name} ${m.username}" => m
}
users = setunion(
var.owners,
var.blocked_users,
var.members,
flatten(local.teams[*].maintainers),
flatten(local.teams[*].members)
)
}
variables.tfでは、モジュールで使用する変数の定義と、それらの変数を元としたローカル値を定義しています。
変数は大きく分けて、以下の値を受け取れるように定義しています。
- Organization名
- Owner(組織ロール)のメンバー一覧
- Member(組織ロール)のメンバー一覧
- ブロックユーザー一覧
- チーム設定
- 既存チーム一覧
これらの変数は、先ほど説明したorganization.tfから値が渡されます。
ローカル値は、以下の用途で利用します。
- memberships: 組織全体のメンバーシップを管理
- ownersリストのメンバーは"admin"、membersリストのメンバーは"member"として設定されます。
- 3つ目のマップに関しては、Maintainer(チームロール)メンバーとMember(チームロール)メンバーの集合から、Owner(組織ロール)のメンバー以外はMember(組織ロール)を付与するようにしています
- この"memberships"は、GitHub Providerのgithub_membershipで利用されます。github_membershipでは、"admin"を指定することでOwner(組織ロール)を割り当てられます。紛らわしい箇所ですので、ご留意ください。
- github_membership Argument Reference
- teams:チームの基本設定を定義
- 名前、説明、可視性、Maintainer(チームロール)のメンバー一覧、Member(チームロール)のメンバー一覧といった項目について、デフォルト値を設定しつつ、organization.tfから渡された値で上書きできるようにしています。
- new_teams:既存チームと新規チームで分離
- teams_maintainers・teams_members: チームメンバーシップを管理
- チームのMaintainer(チームロール)メンバーとMember(チームロール)メンバーの情報を管理します。
- teams_maintainersでは、各チームのmaintainersリストから、"maintainer"として設定
- teams_membersでは、各チームのmembersリストから、"member"として設定
- team_memberships: チームメンバーシップのマッピング
- teams_maintainersとteams_membersを結合し、一意のキーでマッピングします
- キーは"チーム名 ユーザー名"の形式で、チームとユーザーの組み合わせを一意に特定できます
- このマップは、GitHub Providerのgithub_team_membershipで利用され、各チームのメンバーシップを設定します
- users: 全GitHub ユーザー
- Owner(組織ロール)、Member(組織ロール)、ブロックユーザー、全チームのMaintainer(チームロール)とMember(チームロール)を含むすべてのGitHub ユーザーを定義します
- データソースでユーザー情報を取得する際に利用されます
main.tf
data "github_user" "main" {
for_each = local.users
username = each.key
}
# 既存のTeamを取得するデータソース
data "github_team" "existing" {
for_each = var.existing_teams
slug = each.key
}
provider "github" {
owner = var.name
}
resource "github_membership" "main" {
for_each = local.memberships
username = each.key
role = each.value
}
resource "github_organization_block" "main" {
for_each = var.blocked_users
username = each.key
}
# 新規のTeamのみを作成するリソース
resource "github_team" "main" {
for_each = local.new_teams
name = each.key
description = each.value.description
privacy = each.value.visible ? "closed" : "secret"
}
# Team membership の設定を既存・新規両方に対応
resource "github_team_membership" "main" {
depends_on = [github_membership.main]
for_each = local.team_memberships
team_id = contains(var.existing_teams, each.value.team_name) ? data.github_team.existing[each.value.team_name].id : github_team.main[each.value.team_name].id
username = each.value.username
role = each.value.role
}
まず、データソースとして以下の情報を取得します。
data "github_user" "main" {
for_each = local.users
username = each.key
}
data "github_team" "existing" {
for_each = var.existing_teams
slug = each.key
}
- github_user: variables.tfで作成したlocal.usersに含まれる全ユーザーの情報をGitHub APIから取得します
- github_team: 既存チームの情報をGitHub APIから取得します。slugを使用してチームを特定します
次に、GitHub Providerの設定を行います.
provider "github" {
owner = var.name
}
- organization.tfから渡されたOrganization名をownerとして設定します
- organizationは非推奨となり、現在はownerの利用を推奨されております。
- GitHub Provider Argument Reference
- この設定により、以降のリソース操作は全て指定されたOrganizationに対して実行されます
続いて、Organization全体のメンバーシップを管理します
resource "github_membership" "main" {
for_each = local.memberships
username = each.key
role = each.value
}
- variables.tfで作成したlocal.membershipsを使用して、各ユーザーの組織ロールを設定します
- roleが"admin"の場合はOwner権限、"member"の場合はMember権限が付与されます
ブロックされたユーザーの管理も行います。
resource "github_organization_block" "main" {
for_each = var.blocked_users
username = each.key
}
- organization.tfから渡されたblocked_usersに含まれるユーザーをOrganizationからブロックします
新規チームの作成します。
resource "github_team" "main" {
for_each = local.new_teams
name = each.key
description = each.value.description
privacy = each.value.visible ? "closed" : "secret"
}
- variables.tfで作成したlocal.new_teamsに含まれるチームのみを作成します
- チームの可視性は、visibleがtrueの場合は"closed"(Organization内で表示)、falseの場合は"secret"(チームに所属しているメンバーのみ表示)となります
最後に、全てのチームのメンバーシップを管理します。
resource "github_team_membership" "main" {
depends_on = [github_membership.main]
for_each = local.team_memberships
team_id = contains(var.existing_teams, each.value.team_name) ? data.github_team.existing[each.value.team_name].id : github_team.main[each.value.team_name].id
username = each.value.username
role = each.value.role
}
- depends_onにより、組織のメンバーシップの設定が完了してからチームメンバーシップの設定を行います。
- variables.tfで作成したlocal.team_membershipsを使用して、各チームのメンバーシップを設定します
- チームが既存か新規かによって、参照するチームIDを切り替えています
- roleが"maintainer"の場合はMaintainer権限、"member"の場合はMember権限が付与されます
GitHub Actions
GitHub ActionsによってPull Request作成時にterraform plan
、Pull Requestマージ時にterraform apply
が実行されます。
それぞれのワークフローの詳細な内容を解説します。
terraform plan用ワークフロー
name: terraform plan
on:
pull_request:
paths:
- 'terraform/*.tf'
- 'users/*.csv'
- '.github/workflows/*.yml'
jobs:
plan:
name: terraform plan
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
steps:
- name: checkout
uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- name: setup terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.10.5
- uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ap-northeast-1
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
- run: terraform init
working-directory: terraform
- id: fmt
run: terraform fmt
working-directory: terraform
# terraform fmtでフォーマットされた場合、コミットしてプッシュ
- name: Commit changes
run: |
if [[ -n "$(git status -s)" ]]; then
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add .
git commit -m "terraform fmt"
git push origin ${{ github.head_ref }}
else
echo "No changes to commit"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- id: validate
run: terraform validate -no-color
working-directory: terraform
continue-on-error: true
# terraform validate失敗時の結果をプルリクエストに出力
- uses: actions/github-script@v6
if: steps.validate.outcome == 'failure'
env:
STDERR: "```${{ steps.validate.outputs.stderr }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDERR
});
core.setFailed('terraform validate failed');
- name: Make script executable
run: chmod +x ./script.sh
working-directory: shell
- name: Generate GitHub Apps token
id: generate
env:
APP_ID: ${{ secrets.APP_ID }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
run: |
./script.sh
working-directory: shell
- id: plan
run: terraform plan -no-color
working-directory: terraform
continue-on-error: true
env:
GITHUB_TOKEN: ${{ steps.generate.outputs.token }}
# terraform plan成功時・失敗時それぞれの結果をプルリクエストに出力
- uses: actions/github-script@v6
if: steps.plan.outcome == 'failure'
env:
STDERR: "```${{ steps.plan.outputs.stderr }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDERR
});
core.setFailed('terraform plan failed');
- uses: actions/github-script@v6
if: steps.plan.outcome == 'success'
env:
STDOUT: "```${{ steps.plan.outputs.stdout }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDOUT
})
- name: Revoke GitHub Apps token
if: ${{ always() }}
env:
GITHUB_TOKEN: ${{ steps.generate.outputs.token }}
run: |
curl --location --silent --request DELETE \
--url "${GITHUB_API_URL}/installation/token" \
--header "Accept: application/vnd.github+json" \
--header "X-GitHub-Api-Version: 2022-11-28" \
--header "Authorization: Bearer ${GITHUB_TOKEN}"
まず最初に、ワークフローのトリガーを見ていきます。
on:
pull_request:
paths:
- 'terraform/*.tf'
- 'users/*.csv'
- '.github/workflows/*.yml'
terraformやCSV、ワークフロー用ファイルの更新に対してプルリクエストが作成された際に、このterraform plan用のワークフローが実行されます。
次に、権限設定です。
jobs:
plan:
name: terraform plan
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
基本的にはGITHUB_TOKENを利用します。
ただしGITHUB_TOKENはOrganizationのPermissionが設定できないため、Organizationのチームメンバーの管理の処理をするterraform plan
、terraform apply
のステップに関してはGitHub Appsトークンを利用します。
GITHUB_TOKEN のアクセス許可
各権限の利用用途は下記の通りです。
- id-token: write
- GitHubとAWSのOIDC認証用
- contents: write
terraform fmt
でフォーマットされた際にその変更をコミット・プッシュするためwrite権限を利用します。
- pull-requests: write
- Pull Requestへのコメント用
stepsの処理の流れを説明していきます
- 環境準備
- name: checkout
uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- name: setup terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.10.5
- uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ap-northeast-1
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
リポジトリのチェックアウト、Terraformのセットアップ、AWS認証の設定を行います。
AWS認証に関しては、初期構築で作成したS3アクセス用IAMロールを利用します。
- Terraformの初期化・フォーマット・検証
- run: terraform init
working-directory: terraform
- id: fmt
run: terraform fmt
working-directory: terraform
# terraform fmtでフォーマットされた場合、コミットしてプッシュ
- name: Commit changes
run: |
if [[ -n "$(git status -s)" ]]; then
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add .
git commit -m "terraform fmt"
git push origin ${{ github.head_ref }}
else
echo "No changes to commit"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- id: validate
run: terraform validate -no-color
working-directory: terraform
continue-on-error: true
# terraform validate失敗時の結果をプルリクエストに出力
- uses: actions/github-script@v6
if: steps.validate.outcome == 'failure'
env:
STDERR: "```${{ steps.validate.outputs.stderr }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDERR
});
core.setFailed('terraform validate failed');
Terraformの初期化、コードのフォーマット、構文検証を行います。
terraform fmt
によってフォーマットされた場合は、botによるコミット・プッシュが実行されます。
teraform varidate
で失敗した場合は、teraform varidate
の出力結果をPull Requestにコメントとして投稿します。
terraform fmt
とteraform varidate
を組み込むことで、基本的なコマンドをGitHub ホステッド ランナー上で完結するようにしています。
(ローカルでこれらのコマンドを実行する必要がなくなる)
- GitHub Apps トークンの生成
- name: Make script executable
run: chmod +x ./script.sh
working-directory: shell
- name: Generate GitHub Apps token
id: generate
env:
APP_ID: ${{ secrets.APP_ID }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
run: |
./script.sh
working-directory: shell
先ほどの繰り返しとなりますが、Organizationのチームメンバーの管理の処理をするterraform plan
、terraform apply
のステップに関してはGitHub Appsトークンを利用します。
shellフォルダに格納されているGitHub Appsトークン生成用のシェルを実行することで、GitHub Appsトークンを生成しております。
今までの記事で紹介しました通り、GitHub Appsトークン生成スクリプトとGitHub Actionsワークフロー上のGitHub Appsトークン生成に関するステップは、下記の参考記事から引用させていただいております。
GitHub Appsトークン解体新書:GitHub ActionsからPATを駆逐する技術
- terraform planの実行と結果の通知
- id: plan
run: terraform plan -no-color
working-directory: terraform
continue-on-error: true
env:
GITHUB_TOKEN: ${{ steps.generate.outputs.token }}
# terraform plan成功時・失敗時それぞれの結果をプルリクエストに出力
- uses: actions/github-script@v6
if: steps.plan.outcome == 'failure'
env:
STDERR: "```${{ steps.plan.outputs.stderr }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDERR
});
core.setFailed('terraform plan failed');
- uses: actions/github-script@v6
if: steps.plan.outcome == 'success'
env:
STDOUT: "```${{ steps.plan.outputs.stdout }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDOUT
})
そして、生成したGitHub Apps トークンを利用してterraform plan
を実行します。
terraform plan
に関しては、成功事も失敗時も出力結果をPull Requestにコメントとして投稿します。
成功時は、リソースの変更内容が表示されますので、承認者はその変更内容を確認してPull Requestをマージするか判断します。
- GitHub Apps トークンの失効
- name: Revoke GitHub Apps token
if: ${{ always() }}
env:
GITHUB_TOKEN: ${{ steps.generate.outputs.token }}
run: |
curl --location --silent --request DELETE \
--url "${GITHUB_API_URL}/installation/token" \
--header "Accept: application/vnd.github+json" \
--header "X-GitHub-Api-Version: 2022-11-28" \
--header "Authorization: Bearer ${GITHUB_TOKEN}"
最後に不要となったGitHub Apps トークンを失効します。
こちらも下記の参考記事から引用させていただいております。
GitHub Appsトークン解体新書:GitHub ActionsからPATを駆逐する技術
terraform apply用ワークフロー
name: terraform apply
on:
pull_request:
types: [closed]
branches:
- main
paths:
- 'terraform/*.tf'
- 'users/*.csv'
- '.github/workflows/*.yml'
jobs:
apply:
name: terraform apply
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
permissions:
id-token: write
contents: read
pull-requests: write
steps:
- name: checkout
uses: actions/checkout@v3
- name: setup terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.10.5
- uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ap-northeast-1
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
- run: terraform init
working-directory: terraform
- name: Make script executable
run: chmod +x ./script.sh
working-directory: shell
- name: Generate GitHub Apps token
id: generate
env:
APP_ID: ${{ secrets.APP_ID }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
run: |
./script.sh
working-directory: shell
- id: apply
run: terraform apply -auto-approve -no-color
working-directory: terraform
continue-on-error: true
env:
GITHUB_TOKEN: ${{ steps.generate.outputs.token }}
# terraform apply成功時・失敗時それぞれの結果をプルリクエストに出力
- uses: actions/github-script@v6
if: steps.apply.outcome == 'failure'
env:
STDERR: "```${{ steps.apply.outputs.stderr }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDERR
});
core.setFailed('terraform apply failed');
- uses: actions/github-script@v6
if: steps.apply.outcome == 'success'
env:
STDOUT: "```${{ steps.apply.outputs.stdout }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDOUT
})
- name: Revoke GitHub Apps token
if: ${{ always() }}
env:
GITHUB_TOKEN: ${{ steps.generate.outputs.token }}
run: |
curl --location --silent --request DELETE \
--url "${GITHUB_API_URL}/installation/token" \
--header "Accept: application/vnd.github+json" \
--header "X-GitHub-Api-Version: 2022-11-28" \
--header "Authorization: Bearer ${GITHUB_TOKEN}"
terraform apply用ワークフローは、terraform plan用ワークフローとほぼ内容が同じです。
重複することは説明を割愛させていただきます。
最初にワークフローのトリガーを見ていきます。
on:
pull_request:
types: [closed]
branches:
- main
paths:
- 'terraform/*.tf'
- 'users/*.csv'
- '.github/workflows/*.yml'
jobs:
apply:
name: terraform apply
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
terraformやCSV、ワークフロー用ファイルの更新に対するプルリクエストがmainブランチにマージされた際に、このterraform apply用のワークフローが実行されます。
次に、権限設定です。
jobs:
apply:
name: terraform apply
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
permissions:
id-token: write
contents: read
pull-requests: write
terraform plan用のワークフローと違いファイル更新がないため、contentsをread権限にしております。
それ以外の権限はterraform plan用のワークフローと同じです。
stepsの処理の流れは以下の通りです。
terraform apply
を実行するstep以外はほぼ同じです。
(terraform fmt
、terraform validate
を実行するステップがない)
- 環境準備
- Terraformの初期化
- GitHub Apps トークンの生成
- terraform applyの実行と結果の通知
- GitHub Apps トークンの失効
4の「terraform applyの実行と結果の通知」に関しても、
terraform plan
と同様に成功事も失敗時も出力結果をPull Requestにコメントとして投稿します。
- id: apply
run: terraform apply -auto-approve -no-color
working-directory: terraform
continue-on-error: true
env:
GITHUB_TOKEN: ${{ steps.generate.outputs.token }}
# terraform apply成功時・失敗時それぞれの結果をプルリクエストに出力
- uses: actions/github-script@v6
if: steps.apply.outcome == 'failure'
env:
STDERR: "```${{ steps.apply.outputs.stderr }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDERR
});
core.setFailed('terraform apply failed');
- uses: actions/github-script@v6
if: steps.apply.outcome == 'success'
env:
STDOUT: "```${{ steps.apply.outputs.stdout }}```"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.STDOUT
})
最後に
3つの記事に渡る長い長い内容となってしまいましたが、ここまで読んでいただきありがとうございます!
以上、クラウド事業本部の吉田でした!