🎁

GitHub Organizationの所属人数チェックのためにGitHub Actionのアクションを自作する

2021/12/18に公開

概要

こんにちは、M-Yamashitaです。
この記事は、CI/CD Advent Calendar 2021の18日目の記事です。

この記事では、GitHub Organizationの所属人数チェックのために、Organizationのシート数、Terraformのファイル内のメンバー数の確認を行うGitHub Actionのアクションを開発した話を紹介します。

伝えたいこと

  • Organizationへのユーザ招待ごとに、Organizationのシート数を毎回確認する手間があり大変
  • 自作アクションのCheck Members開発
  • 自作アクションと既存のアクションを組み合わせることで、シート数のチェック結果をPull Requestのコメントへ投稿できるようにした

背景

GitHubにはOrganizationという共有アカウントの仕組みがあります。
https://docs.github.com/ja/organizations/collaborating-with-groups-in-organizations/about-organizations

例として、赤枠で囲った部分がOrganizationになります。

このOrganizationに所属することで、複数人で一緒に開発を進められるようになります。

Organizationはユーザあたりの従量課金制となっており、無料・有料プランがあります。
https://github.com/pricing

有料プランでは、Organizationに所属できるユーザ数をシート数として管理しており、管理者が自由にシート数を増減できます。
Organizationに新たにユーザを招待する際、招待後の所属ユーザ数がOrganizationのシート数をオーバーする場合は、管理者がGitHub上でシートを追加する必要があります。
この招待方法として、GitHubの画面を操作して招待する方法以外に、以下のようにTerraformを使用した招待方法もあります。

1.Terraformのファイルにgithub_membershipgithub_repository_collaboratorのリソースを記載
例:

resource "github_membership" "membership_for_some_user" {
  username = "SomeUser"
  role     = "member"
}

2.terraform applyのコマンドを実行

Terraformによる招待方法を使用すると、ファイルを修正しコマンド実行するするだけで、Organizationに所属するユーザを管理できるようになります。

このTerraformを使用した管理方法を経験する機会があり、その際のユーザ招待は以下フローとなっていました。

  1. Terraformのファイルに新規ユーザ名を記載したPull Requestを作成する。
  2. 管理者がレビューし、mainブランチへマージ
  3. mainブランチにマージされると、GitHub Actionsでterraform initterraform applyが実行される
  4. 新規ユーザに招待メールが届く

解決したい課題

背景に記載したフローにおいて、2番目のレビューに問題がありました。
レビューでは、管理者はシート数が足りているか、都度画面を操作して確認する必要があります。この画面の操作では、「GitHub上でSettingsタブに移動」、「Billing Planの選択」、「シート数を目視して判定」といった一連の流れが必要でした。
1年に1回程度のユーザ招待であれば、この手間は苦にはなりづらいのですが、何度も発生していたので面倒な確認作業となっていました。
そのため、この画面遷移の手間や確認の手間をなくし、ユーザ招待のPull Requestを見るだけで現在のシート数、不足シート数を把握できるようにしたいと考えました。

解決手段

Pull Requestを見るだけなので、このリポジトリで使用しているGitHub Actionsを使うことで解決できるはずと考えました。

CIで実施することが決まったので、次はこの課題の解決を行ってくれるツールについて、既存のGitHub Actionsのアクションの流用か、自作のアクションを使用するかのどちらかです。
同じような課題を抱えている人、その課題のためにアクションを作った人がどこかにいるはずと思い、アクションを探してみましたが見つかりませんでした。
そのため自作アクションの選択肢を取りました。

自作の場合、好きなように作れてしまうので、最初の段階は機能を絞り込んでおく必要があります。そうしないと、あれもやりたいこれもやりたいとなり、いつまで経っても出来上がりません。
そのため欲しい機能を列挙後、最低限必要な機能に絞りこんで以下3つにまとめました。

  1. GitHubのOrganization情報から、現在のシート数、最大シート数を取得
  2. Terraformのファイルにあるgithub_membershipgithub_repository_collaboratorのリソースからユーザ数を取得
  3. Pull Requestにシート数とユーザ数をコメントとして投稿、シート不足の場合は不足メッセージも合わせて投稿

この3つに関してアクションの調査と同様に、ライブラリやアクションがないかを調べてみました。
1番目はOctokit、3番目はgithub-scriptのアクションが使えそうだったので採用しましたが、2番目に関してはライブラリやアクションがないようでした。
そのため、2番目のみ取得コードを作ることとしました。

内部処理

解決手段で列挙した、必要な機能の内部処理について説明します。

GitHubのOrganization情報取得

GitHubではAPIが提供されており、APIを通じてOrganization情報を取得できます。
https://docs.github.com/en/rest/reference/orgs#get-an-organization

このAPIを使うためには、admin:orgを付与したGitHubのアクセストークンが必要となっています。

To see many of the organization response values, you need to be an authenticated organization owner with the admin:org scope.

このadmin:orgにはread/write両方の権限が含まれています。
ただ、組織情報を取得するためだけのためにwriteの権限は必要だろうかと考え、試しにadmin:orgのreadのみ権限を付与し、APIを叩いたところ正常に取得できました。
そのため、このアクションではadmin:orgのread権限のみ付与したアクセストークンを使うようにしました。

また、このAPIはOctokit経由で実行できるため、そのgemを使うようにしました。
Octokitで組織情報を取得する場合、以下コードを実行することで取得できます。

client = Octokit::Client.new(access_token: @access_token)
organization = client.org(@organization_name)

なおOctokitでは、API呼び出し後の戻り値の形式は、基本的にSawyer::Resourceとなります。
https://github.com/lostisland/sawyer
そのため、以下コードを使用することで、Organizationの現在のシート数(filled_seats)と最大のシート数(max_seats)を取得できます。

client = Octokit::Client.new(access_token: @access_token)
organization = client.org(@organization_name)
org_plan = organization[:plan]
filled_seats = org_plan[:filled_seats]
max_seats = org_plan[:seats]

ここまでで、シート数の取得は完了です。

Terraformのファイルからユーザ数取得

Terraformでのgithub_membershipgithub_repository_collaboratorリソースの基本形式は以下の通りとなります。

resource "github_membership" "membership_for_some_user" {
  username = "SomeUser"
  role     = "member"
}

参考:
https://registry.terraform.io/providers/integrations/github/latest/docs/resources/membership
https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_collaborator

このアクションで取得したい箇所は、これらリソース内にあるusername = "SomeUser"の部分となります。
ただし、この取得の際にディレクトリにある全てのファイルを読み込んでしまうと、他のリソースが書かれたTerraformのファイルを読み込んでいる可能性があります。
その結果、予想外のusernameを取得してしまうことが考えられるため、その影響を少しでも少なくするために以下の制限をつけることにしました。

制限:

  • github_membershipのリソース、github_repository_collaboratorのリソースを持つファイルをアクションの環境変数で指定する。アクションでは環境変数に指定されたファイルのみからユーザ数を取得する。

この方法でユーザ数を取得できるようになりました。

botで投稿

ここまで説明したシート数とユーザ数を、github-scriptアクションを使用して、Pull Request上に表示します。

このgithub-scriptアクションの中で、script項目に出力内容を記載して、 github.rest.issues.createCommentを使用することでPullRequestにbotとして投稿可能となります。
なお、取得したシート数とユーザ数をこのgithub-scriptアクションに渡す際には、GitHub Actionsでstep内の出力を共有できる仕組みを利用しました。
https://docs.github.com/ja/actions/creating-actions/metadata-syntax-for-github-actions#outputs-for-composite-actions

この3つが内部処理となります。

効果

まだ実践では試せていないため、個人で用意したOrganization上でテストしました。

前提条件:

  • GitHubのOrganizationで埋まっているシート数、最大シート数がそれぞれ1
  • ユーザを5人招待するPull Requestを作成
  • GitHub Actionsのワークフローは、以下の通り
on: [pull_request]

jobs:
  job:
    runs-on: ubuntu-latest
    name: A job to show seats
    steps:
      - uses: actions/checkout@v2

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.7
          bundler-cache: true

      - name: Count seats and members
        id: seats_members
        uses: M-Yamashita01/check-members@v0.5
        env:
          ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
          MEMBERSHIP_FILE_PATH: '${{ github.workspace }}/membership.tf' 
          REPOSITORY_COLLABORATOR_FILE_PATH: '${{ github.workspace }}/repository_collaborator.tf'
          ORGANIZATION_NAME: 'organization_name'

      - name: Post comments
        uses: actions/github-script@v5
        with:
          script: |
            var output = `Current seats in organization and members in terraform files.\n
              ・Seats that membership used:: ${{steps.seats_members.outputs.filled_seats}}\n
              ・Max seats an organization can use: ${{steps.seats_members.outputs.max_seats}}\n
              ・Total number of membership in terraform files: ${{steps.seats_members.outputs.members_in_terraform}}\n\n
            `
            const numberOfSeatsInShortage = ${{steps.seats_members.outputs.members_in_terraform}} - ${{steps.seats_members.outputs.max_seats}}
            var additional_message = `There is no shortage of seats.\n`
            if (numberOfSeatsInShortage > 0) {
              additional_message = `There are ${numberOfSeatsInShortage} seats missing. Please add seats.\n`
            }
            output += additional_message
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

表示結果は以下画像の通りとなり、正常にシート数の表示とシート数不足メッセージが表示できました。

まとめと今後

GitHub Actionsを使用した自作アクションを作成し、シート数とユーザ数を確認できるようにしました。
まだ実践投入ができていませんが、このアクションにより、毎回シート数を確認するためにSettings画面を見る必要がなくなると思われます。

今後は、以下の改善や機能追加を行っていきたいと考えています。

  • 環境変数には、Terraformのファイルがあるパスのみ指定し、ユーザ数を取得できるようにする
  • 取得したusernameが今もGitHub上に存在するかチェックする

2番目に関しては、ユーザ招待時のusernameが間違っていたり、アカウント名の変更によりTerraformのファイルに記載されているusernameが存在しなかったりすることがありえるので、未然にチェックできるようにするためです。

以上となります。今年もAdvent Calendar 2021を楽しんでいきましょう!

Discussion