0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Configuration-as-Code 大量のリポジトリの設定ファイルを一元管理する「ghfanout」を作った

0
Posted at

作ったもの

ghfanout は、.gitignorepom.xmlpyproject.tomlpackage.json のような共有設定ファイルを 1 か所で一元管理(single source of truth)し、多数の GitHub リポジトリへ Pull Request として配布する CLI ツールです。
ファイルを 1 回編集してコマンドを 1 回叩けば、影響するすべてのリポジトリに PR が立ちます。

pip install ghfanout   # または uv tool install ghfanout

解決したい課題

マイクロサービスをリポジトリ分割で運用していると、こんな経験はないでしょうか。

  • .gitignore に 1 行足したいだけなのに、対象リポジトリが 10 個ある
  • CI の設定を直したはずなのに、直っていないリポジトリが後から見つかる
  • 「あのリポジトリだけ pyproject.toml の lint ルールが古い」が定期的に発生する

モノレポに統合できればこの問題は消えますが、組織やデプロイの都合でリポジトリを分けざるを得ないケースは多くあります。ghfanout はそういう「モノレポにできないリポジトリ群」のための、設定ファイルの一元管理・配布ツールです。

既存ツールを検討した結果

私のニーズは「あるべき状態を宣言的に管理して、差分が出たときだけ PR を配り続ける。」こと。
加えて、私の環境では GitHub Actions が使えないという制約もありました。
既存ツールはそれぞれ少しずつ噛み合いませんでした。

  • safe-settings — ブランチ保護やラベルなどリポジトリ「設定」の Policy-as-Code。リポジトリ内のファイルは配布できない
  • multi-gitter / git-xargs — clone してスクリプトを実行するワンショットの一括変更ツールで、あるべき状態を継続的に管理するものではない
  • repo-file-sync-action — 一番近い存在だが GitHub Actions 前提

この隙間を埋めるのが ghfanout です。
safe-settings がリポジトリ設定(メタデータ)の Policy-as-Code なら、ghfanout はリポジトリ内ファイルの Configuration-as-Code。両者は組み合わせて使えます。

特徴

1. 対象リポジトリを git clone しない

配布はすべて GitHub Git Data API 経由で行います。対象リポジトリをローカルに clone しないので、リポジトリが何十個あってもディスクも時間も消費しません。pip install するだけで動きます。

2. Kustomize 風の base + overlay 構成

Kubernetes の Kustomize を使ったことがある人にはおなじみの、「base に共通の内容を置き、overlay で配布先ごとの選択をする」構成を採用しています。

ghfanout.yaml                # 配布先の host / org を宣言
base/
  common/                    # 全リポジトリ共通。無条件に配布される
    .gitignore
  java-service/              # プロファイル: opt-in したリポジトリだけに配布
    pom.xml.tmpl             # .tmpl = Jinja2 テンプレート。リポジトリごとに描画される
  python-service/
    pyproject.toml
  node-service/
    package.json
overlays/
  user-service/              # ディレクトリ名 = 配布先リポジトリ名
    manifest.yaml            # このリポジトリが受け取るプロファイル・ブランチを宣言
  api-gateway/
    manifest.yaml

manifest.yaml には以下を記載します。

bases:                 # 受け取るプロファイル(common/ は常に含まれる)
  - java-service
branches:              # 配布先ブランチ(省略時はデフォルトブランチ)
  - main
  - release-1.x
values:                # テンプレートから参照する値
  version: "1.2.3"

3. Jinja2 テンプレートでリポジトリごとの値を埋め込める

拡張子が .tmpl のファイルだけが Jinja2 テンプレートとして描画され、拡張子を除いた名前で配布されます(pom.xml.tmplpom.xml)。

<!-- base/java-service/pom.xml.tmpl -->
<groupId>com.example</groupId>
<artifactId>{{ repo }}</artifactId>
<version>{{ values.version | default("0.1.0") }}</version>

.tmpl が付いていないファイルはそのままコピーされます。

使ってみる

# 1. 設定リポジトリの雛形を生成(動くサンプル付き)
ghfanout init ./my-config --org myorg
cd my-config

# 2. base + overlay をローカルで合成して確認(git 操作なし・ネットワーク不要)
ghfanout build example-service           # 出力: dist/example-service/

# 3. 認証して、実リポジトリとの差分をプレビューしてから配布
export GHFANOUT_TOKEN=ghp_xxxx
ghfanout deploy example-service --dry-run
ghfanout deploy example-service          # 差分があれば PR が開く

# 全リポジトリへ一括配布も可能
ghfanout deploy --all

deploy は配布先ブランチごとに差分を計算し、差分のあるブランチにだけ PR を作ります。
作業ブランチ(ghfanout/update-<branch>)がすでにあれば作り直し、open な既存 PR があれば新規作成せず再利用するので、何度実行しても PR が乱立しません。

PR を経由せず対象ブランチへ直接 push する deploy_mode: push も選べます(信頼できる社内リポジトリ向け)。

おわりに

「設定ファイルを直すたびに 10 個のリポジトリを回る」作業から解放されたい方は、ぜひ試してみてください。

pip install ghfanout
ghfanout init --org <your-org>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?