アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Githubのラベル定義を宣言的に集中管理してみた

morishitaです。

当社で最も開発が活発なリポジトリはメインサービスであるいこーよのものです。 毎日、いくつものプルリクエストが登録されてはリリースされクローズしてきます。

プルリクエストの状態や重要度はラベルで表しています。

例えば次のようなラベルがあります。

  • WIP
  • エンジニアレビュー待ち
  • デザイナーレビュー待ち1
  • レビュー修正待ち
  • Ship it!
  • Bug
  • dependencies2

リポジトリごとにラベルとその色がまちまちだと状況を把握しにくいので、いこーよ以外のプロダクトも概ね同じラベルを登録して使っています。

これまでいこーよのリポジトリに揃えるべくそれぞれのリポジトリでちまちま手で設定をしてきました。

しかし、このところ Github にいくつか新たなリポジトリを作る機会があり、 流石に面倒なので複数のリポジトリのラベルを一元的に管理できる仕組みを作ってみました。

作ったもの

次のようなYAMLファイルで定義されたラベルを複数のリポジトリに適用するGithub Action とそれを使ったワークフローを作りました。

# ラベルの定義
labels:
  - name: "WIP"
    description: "作業中"
    color: "d4c5f9"
  - name: "レビュー待ち"
    description: "レビュー待ち"
    color: "bfe5bf"
  - name: "プログラマレビュー待ち"
    description: "レビュー待ち"
    color: "bfe5bf"
  - name: "デザイナレビュー待ち"
    description: "レビュー待ち"
    color: "bfdadc"
  - name: "レビュー修正待ち"
    description: "指摘事項を修正してください"
    color: "fef2c0"
  - name: "Ship it!"
    description: "リリースしましょう"
    color: "c7def8"
  - name: "dependencies"
    description: "Pull requests that update a dependency file"
    color: "0366d6"
  - name: "bug"
    description: "Something isn't working"
    color: "d73a4a"

# リポジトリ毎のラベル設定
repos:
  - name: actindi/repo1
    # labels で定義したラベルから設定する
    labels:
      - WIP
      - レビュー待ち
      - レビュー修正待ち
      - Ship it!
      - dependencies
  - name: actindi/repo2
    labels:
      - WIP
      - プログラマレビュー待ち
      - デザイナレビュー待ち
      - レビュー修正待ち
      - Ship it!

labels 以下でラベルそのものを次の項目で定義します。

  • name
  • description
  • color

name がキーになります。

repos 以下ではそれぞれのリポジトリのラベルを設定しています。 labels 以下で定義したラベルから各リポジトリで必要なものを選択します。

実際にラベルを設定する処理にはgithub-labelerを利用しています3。 これは Golang で作られたCLIツールで、YAML通りにラベルの登録、更新、削除を行ってくれます。
dry-run の機能も備えています。

Github Actions で実行する

github-labeler を実行する仕組みである Github Action について説明します。

Github Action の作成

今回のGihub Actionを構成するファイルは次の3つ。

/
└ .github/
  └ actions/
      ├ action.yml
      ├ Dockerfile
      └ entrypoint.sh

まずは Github Action の定義ファイル actions.yml は次のとおりです。

name: 'Github Labeler'
description: 'Githubリポジトリのラベルを一斉に作成・変更・削除します'
author: 'actindi'
inputs:
  github_token:
    description: 'GITHUB_TOKEN.'
    required: true
  manifest:
    description: 'YAML file to be described about labels and repos.'
    required: false
    default: 'labels.yaml'
  dry_run:
    description: dry run flag
    required: false
    default: 'false'

runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.github_token }}
    - ${{ inputs.manifest }}
    - ${{ inputs.dry_run }}
branding:
  icon: 'alert-octagon'
  color: 'blue'

inputs で次の3つのパラメータを定義しました。 - github_token: ラベル編集APIを使うためのGithubトークン - manifest:前述のラベル定義YAMLファイルのパス(デフォルト値はlabels.yml) - dry_run:dry-run か否かのフラグ。

runs 以下は処理の実行系についての定義ですが、見ての通りアクションの実行は Docker で行います。
そのためのDockerイメージをビルドする Dockerfile は次のとおりです。

FROM golang:1.13-alpine AS builder

RUN set -eux; \
      apk add --no-cache --virtual .build-deps git
RUN go get github.com/b4b4r07/github-labeler

FROM alpine
COPY --from=builder /go/bin/github-labeler /usr/bin/github-labeler
COPY entrypoint.sh /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

マルチステージビルドでイメージを極力小さくしています。

そしてエントリポイントとなるスクリプトは社内ツールということでちょっと雑ですが次の通りです。

#!/bin/sh

cd "$GITHUB_WORKSPACE"

export GITHUB_TOKEN=${INPUT_GITHUB_TOKEN}

if [ ${INPUT_DRY_RUN} = 'true' ] ; then
  github-labeler -manifest ${INPUT_MANIFEST} -dry-run > results.txt
  RUN_RESULT=$?
else
  github-labeler -manifest ${INPUT_MANIFEST} > results.txt
  RUN_RESULT=$?
fi

echo ''
echo '## RESULTS ##################################'
cat results.txt

exit ${RUN_RESULT}

actions.yml で定義したパラメータはプレフィックス INPUT_ の環境変数からそれぞれ取得できる仕組みとなっています(Github Action の仕様)。 INPUT_DRY_RUNtrue ならば dry-run で実行、それ以外では実際にラベルを更新します。

ワークフロー

上記のActionを利用したワークフローについて説明します。

前述のYAMLファイルを管理するリポジトリで、プルリクエストを作ると dry-run で実行して結果を示し、masterにマージすると実際に適用するようにしました。

そのためにワークフローは次の2種類を用意しました。

  • dry-run.yml : プルリクエストにPushするたびに実行する
  • apply.yml : masterにマージしたときに実行する

dry-run.yml

dry-run.yml は次のとおりです。

name: github-labeler
on:
  pull_request:
    types: [opened, synchronize]
jobs:
  github-labeler:
    name: dry-run
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: github-labeler
        uses: ./.github/actions/github-labeler
        with:
          github_token: ${{ secrets.admin_token }}
          dry_run: true

プルリクエストを作成したとき、コミットをPushしたときに実行されるトリガーを設定しています。

uses: ./.github/actions/github-labeler で同じリポジトリで定義されている先程のアクションを利用するよう指定しています。 Gihubのドキュメントではパブリックな公開アクションを利用する例の説明が多く、同じリポジトリで定義したプライベートなアクションを利用する方法がわかりにくいのですが。この様に相対パスでアクション定義のディレクトリを指定すれば良いです。

プルリクの段階ではまだラベルを変更したくないので dry_run: true として dry-run にて実行し、結果を確認できるようにしています。

apply.yml

そして、apply.yml は次のとおりです。

name: github-labeler
on:
  push:
    branches:
      - master
jobs:
  github-labeler:
    name: apply
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: github-labeler
        uses: ./.github/actions/github-labeler
        with:
          github_token: ${{ secrets.admin_token }}

トリガーon.push.branch[master] は master にPushしたときに実行されるということです。 これでマージしたときにも実行されます。
実行すると dry-run ではないので、実際に適用します。

Github トークンについて

Github Actionのワークフローでは secrets.github_token に自動的にトークンが設定されます。それを使ってGithubのAPIを叩いたりできる様になっています。 ただし、このトークンのスコープはワークフローを実行するリポジトリのみとなっています。 今回の場合、別のリポジトリのラベルの編集をしたいのでこれでは足りません。
そのため、以前からCI専用に使っているユーザの Personal access tokenadmin_tokenという名前でリポジトリの Secrets に登録して使っています。 CI専用のユーザが Collaborators として追加されているリポジトリのラベルの編集を行うことができます。ラベルを変更するのですが、admin 権限は不要で write 権限のみで OK です。

実行の様子

プルリクエストにPushすると、ワークフローが実行され次のような表示で結果が示されます。

f:id:HeRo:20191112172622p:plain
Detail LInk

矢印で示したDetailsリンクをたどると次のように実行ログが参照でき、そこから実行結果を見ることができます。

f:id:HeRo:20191112173049p:plain
実行結果

上の画像はdry-runの結果ですが、適用するとどのラベルが作られるのか、あるいは変更・削除されるのかを確認できます。

まとめ

今回は、Githubのラベルを宣言的に定義できるツールを作成して、複数リポジトリの運用をちょっぴり便利にしてみました。

Github Action の作り方を学んでおきたいという個人的欲求も満たされました。

最後に

アクトインディではエンジニアを募集しています。 actindi.net


  1. アクトインディではデザイナーがJS以外のフロントエンドをコーディングするのでデザイナーもPRを作って、レビューして、リリースしていきます。

  2. Dependabotが作るPRにつけられるラベルです。

  3. 実はこのライブラリの作者はもうこのCLIツールをメンテナンスする気はなさそうです。Investigate similar projectで別のツールを使ったほうがいいと言っています。しかし、紹介されているツールはリポジトリごとにラベルの定義自体を行う形式のようです。やりたいのは定義を一元管理して、各リポジトリで利用するラベルを設定すること(定義は一つ、利用は複数リポジトリ)なのでgithub-labelerを使うことにしました。