ghas-setup を flake パッケージとして追加し README をパッケージ単位に分割#14
Conversation
org の GHAS / セキュリティ設定を gh api で一括適用する engine を、利用 repo で 枯らしたうえで devtools へ移設する。移植にあたり次を是正した。 - config パスを「スクリプト自身の場所」基準から cwd 基準 (git rev-parse --show-toplevel) に変更し、env-init と揃える - ファイル名を setup-ghas から ghas-setup へ(topic-first、env-init と揃える) - ヘッダコメントを汎用文言にし、特定 org・非公開 Issue への言及を除去 devtools 自身も .github/ghas.yml を持ち path:.#ghas-setup で dogfood する。 ツールが 2 つになったため README を pkg 単位に分割し、ルートは目次に絞った。 Co-Authored-By: Claude Opus 4.8 <[email protected]>
There was a problem hiding this comment.
Pull request overview
devtools に GitHub org の GHAS / Actions セキュリティ設定を gh api で一括適用する新ツール ghas-setup を Nix flake パッケージとして追加し、ツール別 README へ情報を分割してドキュメント構成を整理する PR です(devtools 自身の dogfooding 用に .github/ghas.yml も追加)。
Changes:
pkgs/ghas-setup/を新設し、ghas-setupスクリプト・Nix 包装・bats テストを追加- devtools 自身の適用例として
.github/ghas.ymlを追加 - ルート README を目次化し、各ツール README(env-init / ghas-setup)に一次情報を移動
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | ルート README を目次+共通事項中心に整理し、各パッケージ README へ誘導 |
| pkgs/ghas-setup/tests/ghas-setup.bats | ghas-setup の引数パース / pre-flight / --dry-run を検証する bats テスト追加 |
| pkgs/ghas-setup/README.md | ghas-setup の前提・使い方・config 仕様を明記 |
| pkgs/ghas-setup/package.nix | makeWrapper で gh / git / yq-go 等を同梱する Nix パッケージ定義追加 |
| pkgs/ghas-setup/ghas-setup | ghas-setup 本体スクリプト追加(git worktree ルート基準で config 解決など) |
| pkgs/env-init/README.md | env-init の一次情報をパッケージ README として分離 |
| flake.nix | packages/checks に ghas-setup を追加(shellcheck / bats / nixfmt 対象拡張) |
| devbox.json | dogfood 用に path:.#ghas-setup と依存ツールを追加 |
| .github/ghas.yml | devtools 自身の dogfood 兼サンプルとして org ポリシー YAML を追加 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| --config) | ||
| CONFIG="$2"; shift 2 ;; |
There was a problem hiding this comment.
対応しました。--config が値無し(末尾)の場合に usage を出して exit 1 する分岐を追加し、bats テストも足しました(5a964b4)。
| ORG=$(yq '.org' "$CONFIG") | ||
| CONFIG_NAME=$(yq '.configuration.name' "$CONFIG") | ||
| CONFIG_DESC=$(yq '.configuration.description' "$CONFIG") | ||
| ENFORCEMENT=$(yq '.configuration.enforcement' "$CONFIG") | ||
| DEFAULT_FOR_NEW_REPOS=$(yq '.default_for_new_repos' "$CONFIG") | ||
| ATTACH_SCOPE=$(yq '.attach_existing_repos' "$CONFIG") | ||
| ACTIONS_DEFAULT_PERMS=$(yq '.actions.default_workflow_permissions' "$CONFIG") | ||
| ACTIONS_APPROVE_PR=$(yq '.actions.can_approve_pull_request_reviews' "$CONFIG") |
There was a problem hiding this comment.
検証したうえで -e -r は採用しません(yq-go v4.53.2 で確認)。
- クォート: yq-go(mikefarah)は既定で scalar を raw 出力するため、
"や:を含む値でもクォートは付きません。-rは no-op で不要です。 -eによる必須キー検証:can_approve_pull_request_reviews: falseのように正当な値がfalseの必須キーに対してyq -eが exit 1 を返し、正常設定を誤って失敗扱いにするリグレッションになります。- 欠落キーの
null:--dry-runでorg: null等がそのまま表示され適用前に気づけること、移植元で実績のある挙動であることから現状維持とします。
| ) | ||
| while IFS= read -r entry; do | ||
| CONFIG_BODY_FIELDS+=(-f "$entry") | ||
| done < <(yq '.configuration.scanners | to_entries | .[] | .key + "=" + .value' "$CONFIG") |
There was a problem hiding this comment.
yq-go は既定で raw 出力のため、to_entries での key=value 生成にもクォートは混ざりません(検証済み)。-r は no-op で不要のため現状維持とします。
| echo " name: $CONFIG_NAME" | ||
| echo " enforcement: $ENFORCEMENT" | ||
| echo " scanners:" | ||
| yq '.configuration.scanners | to_entries | .[] | " " + .key + ": " + .value' "$CONFIG" |
There was a problem hiding this comment.
同上です。--dry-run の scanner 表示も yq-go の既定 raw 出力でクォートは付かないため、現状維持とします。
| CONFIG_ID=$(gh api "/orgs/$ORG/code-security/configurations" \ | ||
| --jq ".[] | select(.name == \"$CONFIG_NAME\") | .id" | head -n1) |
There was a problem hiding this comment.
対応しました。select(.name == env.GHAS_CONFIG_NAME) に変更し、name を env 経由で渡すことで " や \ を含んでも jq 式が壊れないようにしました(5a964b4)。
| # ghas-setup を PATH に載せて bats を実行(gh api を叩く本適用パスは認証が要るため | ||
| # 検証せず、引数パース・pre-flight・--dry-run のみ。gh は不要)。 | ||
| ghas-setup-bats = |
There was a problem hiding this comment.
対応しました。コメントを「テストは gh を呼ばない(pre-flight より手前で exit)が、wrap 済みパッケージを使うため closure には gh が入る」旨に修正しました。raw スクリプトの直実行は shebang /usr/bin/env が Nix の hermetic build sandbox で解決できず(env-init と同じ理由)採用せず、wrap 済みパッケージでテストします(5a964b4)。
- --config の値欠落時に set -u の unbound variable で落ちる問題を、 usage を出して exit 1 する分岐で防ぐ(テストも追加) - 既存 configuration lookup の jq 式に config 由来の name を直埋めせず、 env 経由で渡して " や \ を含んでも壊れないようにする - ghas-setup-bats の「gh は不要」コメントを、closure には gh が入るが テストでは起動しない旨に修正 Co-Authored-By: Claude Opus 4.8 <[email protected]>
| CONFIG_ID=$(GHAS_CONFIG_NAME="$CONFIG_NAME" gh api "/orgs/$ORG/code-security/configurations" \ | ||
| --jq '.[] | select(.name == env.GHAS_CONFIG_NAME) | .id' | head -n1) |
There was a problem hiding this comment.
対応しました。lookup に --paginate を追加し、org 内の configuration が 30 件(既定 per_page)を超えても全件から既存を検索して重複作成を防ぐようにしました(805aca3)。
| -f "default_for_new_repos=$DEFAULT_FOR_NEW_REPOS" --silent | ||
|
|
||
| # 既存 repo に attach | ||
| echo "==> 既存 repo 全件に attach" |
There was a problem hiding this comment.
対応しました。attach ログを ==> 既存 repo に attach (scope=$ATTACH_SCOPE) に変更し、none 等でも実際の適用範囲が分かるようにしました(805aca3)。
- 既存 configuration の lookup に --paginate を追加し、org 内の configuration が 30 件を超えても全件から検索して重複作成を防ぐ - 既存 repo への attach ログを実際の scope を含む表現に修正 (attach_existing_repos: none 等で「全件」と誤表示しない) Co-Authored-By: Claude Opus 4.8 <[email protected]>
| gh api --method PUT "/orgs/$ORG/actions/permissions/workflow" \ | ||
| -F "default_workflow_permissions=$ACTIONS_DEFAULT_PERMS" \ | ||
| -F "can_approve_pull_request_reviews=$ACTIONS_APPROVE_PR" \ | ||
| --silent |
There was a problem hiding this comment.
現状のままで正しいため不採用とします。gh api --help で確認したところ、型変換(magic type conversion: true/false/null/数値)を行うのは -F/--field、常に文字列で送るのは -f/--raw-field です(ご指摘とフラグの対応が逆になっています)。本スクリプトの actions ブロックは既に -F を使っており、can_approve_pull_request_reviews=false は boolean に変換されます。提案どおり -f にすると逆に "false" 文字列で送ってしまうため変更しません。
| @@ -0,0 +1,39 @@ | |||
| # ghas-setup を makeWrapper で wrap する。生スクリプト (./ghas-setup) は libexec にそのまま置き、 | |||
| # 外側の wrapper で runtimeInputs を PATH 先頭に prefix して exec するだけ(中身は無改変)。 | |||
There was a problem hiding this comment.
対応しました。「中身は無改変」→「ロジックは無改変。shebang のみビルド時に絶対 bash へ差し替える」に修正しました(d351758)。
shebang を substituteInPlace で差し替えるため「中身は無改変」は厳密には 不正確。shebang のみ差し替える旨を明記する(Copilot レビュー指摘)。 Co-Authored-By: Claude Opus 4.8 <[email protected]>
| CONFIG_ID=$(GHAS_CONFIG_NAME="$CONFIG_NAME" gh api --paginate "/orgs/$ORG/code-security/configurations" \ | ||
| --jq '.[] | select(.name == env.GHAS_CONFIG_NAME) | .id' | head -n1) |
There was a problem hiding this comment.
対応しました。--paginate | head -n1 をやめ、--paginate --slurp で全ページを 1 配列にまとめてから jq 側で最初の一致 id を取り出す形([.[][] | select(...) | .id] | .[0] // "")に変更しました(00793f0)。head で stdout を途中で閉じることがなくなり、pipefail + SIGPIPE で落ちる懸念を解消しています。
| coreutils, | ||
| git, | ||
| gh, | ||
| yq-go, | ||
| }: |
There was a problem hiding this comment.
対応しました。パッケージ引数に gnugrep を追加し、wrapper の PATH prefix(lib.makeBinPath)にも同梱しました(00793f0)。admin:org scope 判定の grep がこれで解決します。
| lib.makeBinPath [ | ||
| coreutils | ||
| git | ||
| gh | ||
| yq-go | ||
| ] |
There was a problem hiding this comment.
対応しました(F 点と同一論点)。gnugrep をパッケージ引数と makeBinPath の両方に追加し、ビルド後の wrapper に gnugrep が PATH 同梱されていることを確認済みです(00793f0)。
- 既存 configuration lookup の `--paginate | head -n1` を `--paginate --slurp` + jq に変更。pagination 途中で head が stdout を閉じると gh が SIGPIPE で 非 0 終了し set -o pipefail でスクリプトが落ちうるため、head を使わず jq 側で最初の一致 id を取り出す - admin:org scope 判定で使う grep を gnugrep として PATH に同梱 (coreutils に grep は含まれず、Nix 環境で実行時失敗しうるため) Co-Authored-By: Claude Opus 4.8 <[email protected]>
| # pre-flight: gh CLI 認証 | ||
| gh auth status -h github.com >/dev/null 2>&1 || { | ||
| echo "gh CLI が未認証です。'gh auth login' を実行してください。" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| # pre-flight: admin:org scope の有無 | ||
| gh auth status -h github.com 2>&1 | grep -q "admin:org" || { | ||
| echo "gh CLI に admin:org scope がありません。'gh auth refresh -h github.com -s admin:org' を実行してください。" >&2 | ||
| exit 1 | ||
| } |
There was a problem hiding this comment.
対応しました(92edbfe)。gh をスタブして実適用パスを自動テストするケースを 2 件追加しました。
- 新規作成分岐: lookup が空 → POST で作成し、作成 id への defaults/attach/actions 適用と、
default_for_new_repos/scope/can_approve_pull_request_reviews等の field が config 値で渡ること、PATCH を叩かないことを検証 - 更新分岐: lookup が既存 id を返す → PATCH 更新し、新規作成 POST を叩かないことを検証
実装メモ: wrapper は gh を PATH 先頭に prefix するため wrap 済みバイナリでは stub で上書きできません。生スクリプトを bash で直起動(shebang を回避)して PATH 上の stub gh を使う方式にし、flake の bats check に gnugrep と生スクリプトのパス(GHAS_SETUP_RAW)を渡しています。
引数パース・pre-flight・--dry-run に加え、create/update 分岐と gh api の 呼び出し列をカバーする。gh をスタブ(呼び出し引数を記録しダミー JSON を返す) し、生スクリプトを bash で直起動して検証する。wrapper は gh を PATH 先頭に prefix するため stub で上書きできないが、bash 直起動なら shebang を回避でき PATH 上の stub が使われる。flake の bats check に gnugrep と生スクリプトの パス (GHAS_SETUP_RAW) を渡す。 Co-Authored-By: Claude Opus 4.8 <[email protected]>
| echo "==> これから叩く API:" | ||
| echo " POST|PATCH /orgs/$ORG/code-security/configurations (name / description / enforcement / scanners)" |
There was a problem hiding this comment.
対応しました(d9bdb4d)。POST|PATCH の 1 行を 2 行に分け、POST /orgs/$ORG/code-security/configurations(既存が無ければ新規作成) と PATCH /orgs/$ORG/code-security/configurations/<id>(既存があれば更新) を別表示にしました。
| GHAS_SETUP="${GHAS_SETUP:-ghas-setup}" | ||
|
|
There was a problem hiding this comment.
対応しました(d9bdb4d)。GHAS_SETUP_RAW="${GHAS_SETUP_RAW:-$BATS_TEST_DIRNAME/../ghas-setup}" の既定を入れ、flake check 以外(repo で bats pkgs/ghas-setup/tests を直接実行)でも隣の生スクリプトを指すようにしました。未指定での実行で全 8 テスト pass を確認済みです。
- --dry-run の「これから叩く API」が POST|PATCH を 1 行にまとめており、 更新時の PATCH .../<id> と作成時の POST を取り違えやすかったため分けて表示 - bats の GHAS_SETUP_RAW に未設定時の既定(隣の生スクリプト)を入れ、 repo で bats を直接実行しても実適用パステストが動くようにする Co-Authored-By: Claude Opus 4.8 <[email protected]>
| echo "usage: ghas-setup [--config <path>] [--dry-run]" >&2 | ||
| exit 1 | ||
| fi | ||
| CONFIG="$2"; shift 2 ;; |
There was a problem hiding this comment.
検討のうえ不採用とします。明示的な --config <path> 引数の相対パスは cwd 基準で解決するのが一般的な CLI 慣行で(--config ./other.yml はシェルの cwd 基準を期待する)、これを REPO_ROOT 基準に書き換える方がかえって驚きを生みます。デフォルト値だけが REPO_ROOT 基準なのは、cwd の文脈を持たず「この repo を対象にする」前提だからで、ユーザ入力の明示パスとは役割が異なります。
サブディレクトリから repo 相対っぽいパスを渡した場合も、見つからなければ config file が見つかりません で明確に exit 1 し、見つかれば「ユーザが明示的に指したファイル」なので誤適用にはなりません。加えて本ツールは適用前に --dry-run(org 名等を表示)を挟む運用なので、取り違えはそこで気づけます。非標準のパス書き換えロジックを足さない方針(YAGNI)です。
| fi | ||
| CONFIG="$2"; shift 2 ;; | ||
| --config=*) | ||
| CONFIG="${1#*=}"; shift ;; |
There was a problem hiding this comment.
K 点(--config <path>)と同一論点のため同じ判断で不採用とします。--config=<path> でも相対パスは cwd 基準のままにします(明示パス引数の標準的挙動)。デフォルトのみ REPO_ROOT 基準である理由・誤適用しない理由・--dry-run の安全網は K 点の返信のとおりです。
|
|
||
| # pre-flight: yq(mikefarah 版 = devbox の yq-go) | ||
| command -v yq >/dev/null 2>&1 || { | ||
| echo "yq が見つかりません。'devbox shell' 内で実行してください(devbox.json の yq-go)。" >&2 |
There was a problem hiding this comment.
対応しました(1e5510a)。メッセージを「mikefarah 版 yq(yq-go)が必要。wrapper 経由(nix run .#ghas-setup / devbox)なら同梱、生スクリプト直使用時はインストール」と一般化しました。
mikefarah 版 yq(yq-go)が必要である旨と、wrapper 経由(nix run / devbox) なら同梱される旨・生スクリプト直使用時はインストールが要る旨を明示する。 Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
LGTM 👍 Copilot レビュー(計7巡・17指摘)に対応し、コメントが収束したためマージします。
|
ユーザー指示の要点
利用 repo で枯らした
bin/setup-ghas(org の GHAS / セキュリティ設定をgh apiで一括適用する engine)を devtools へ移植する。移植にあたり次を是正する。REPO_ROOT=$(git rev-parse --show-toplevel))に変更し env-init と揃えるsetup-ghas(V-O)→ghas-setup(topic-first)にリネーム(env-init と揃える)追加要望:
.github/ghas.ymlを追加し env-init と同様に dogfood する変更の概要
pkgs/ghas-setup/新設(env-init と同じ Nix パッケージ構造)ghas-setup: 本体ロジックは移植元のまま保持し、上記 3 点のみ是正package.nix:makeWrapperでcoreutils/git/gh/yq-goを PATH に同梱tests/ghas-setup.bats: 引数パース・pre-flight・--dry-runを検証(gh api を叩く本適用パスは認証が要るため対象外).github/ghas.yml新設: devtools 自身のポリシー(org: airs)。dogfood 兼・利用側コピー用の書式例flake.nix:packages/checks(build・shellcheck・ghas-setup-bats・nixfmt)に ghas-setup を追加devbox.json:path:.#ghas-setup・gh@2・yq-go@4を追加(org を変更するため init_hook には入れない)pkgs/<name>/README.mdに移し、ルート README は目次 + 共通事項(採用方式・開発・バージョニング)に絞るレビューのポイント
pkgs/ghas-setup/ghas-setup)。SCRIPT_DIRを廃し git 管理外実行時は明示エラーで exitadmin:org認証を要するため)<ref>やバージョニングの説明はルートに一本化し、各 pkg README から参照検証方法
devbox run check(=nix flake check)→ all checks passed(shellcheck×2スクリプト・env-init bats・ghas-setup bats・statix・deadnix・nixfmt、両パッケージ build)nix fmt→ 差分なしnix run .#ghas-setup -- --dry-runがルートの.github/ghas.yml(org: airs)を cwd 基準で解決し、gh を呼ばず適用予定を表示することを確認🤖 Generated with Claude Code