From d5db21ca13984894102fab8bfaa583d1b07ed710 Mon Sep 17 00:00:00 2001 From: Masakuni Kato <7091+mackato@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:19:56 +0900 Subject: [PATCH 1/8] =?UTF-8?q?ghas-setup=20=E3=82=92=20flake=20=E3=83=91?= =?UTF-8?q?=E3=83=83=E3=82=B1=E3=83=BC=E3=82=B8=E3=81=A8=E3=81=97=E3=81=A6?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=81=97=20README=20=E3=82=92=E3=83=91?= =?UTF-8?q?=E3=83=83=E3=82=B1=E3=83=BC=E3=82=B8=E5=8D=98=E4=BD=8D=E3=81=AB?= =?UTF-8?q?=E5=88=86=E5=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/ghas.yml | 46 ++++++++ README.md | 41 ++----- devbox.json | 5 +- flake.nix | 27 ++++- pkgs/env-init/README.md | 33 ++++++ pkgs/ghas-setup/README.md | 62 +++++++++++ pkgs/ghas-setup/ghas-setup | 151 ++++++++++++++++++++++++++ pkgs/ghas-setup/package.nix | 39 +++++++ pkgs/ghas-setup/tests/ghas-setup.bats | 89 +++++++++++++++ 9 files changed, 460 insertions(+), 33 deletions(-) create mode 100644 .github/ghas.yml create mode 100644 pkgs/env-init/README.md create mode 100644 pkgs/ghas-setup/README.md create mode 100755 pkgs/ghas-setup/ghas-setup create mode 100644 pkgs/ghas-setup/package.nix create mode 100644 pkgs/ghas-setup/tests/ghas-setup.bats diff --git a/.github/ghas.yml b/.github/ghas.yml new file mode 100644 index 0000000..5a1c78a --- /dev/null +++ b/.github/ghas.yml @@ -0,0 +1,46 @@ +# GHAS / Actions セキュリティ設定(declarative policy) +# +# engine: ghas-setup がこのファイルを読んで `gh api` に適用する。 +# ポリシー値(どの scanner を有効にするか・enforcement・Actions 権限)はすべてここに集約し、 +# engine 側はロジックだけを持つ。git diff だけでポリシー変更意図が追えるようにインラインで文書化する。 +# 各項目の意味は https://docs.github.com/en/rest/code-security/configurations を参照。 +# +# これは devtools 自身の dogfood config 兼・利用側がコピーする書式例。利用側は org を自分の org に変える。 + +org: airs + +configuration: + name: airs-default + description: airs org default code security configuration. Managed by ghas-setup. + # enforced: repo 側で設定をオーバーライド不可に強制する(org ポリシーを全 repo へ確実に適用) + enforcement: enforced + scanners: + # GHAS 本体。code scanning / secret scanning 等の前提となるため有効化 + advanced_security: enabled + # 依存グラフ。Dependabot alerts / security updates の前提 + dependency_graph: enabled + # 既知 CVE を含む依存を検知してアラート + dependabot_alerts: enabled + # CVE 修正版への PR を自動生成(サプライチェーン防御の中核) + dependabot_security_updates: enabled + # コミット履歴・push 内のシークレット検知 + secret_scanning: enabled + # シークレットを含む push をブロック(漏洩を未然に防ぐ) + secret_scanning_push_protection: enabled + # 検知したシークレットが有効(生きている)かを発行元 API で検証 + secret_scanning_validity_checks: enabled + # 既知パターン外の汎用シークレット(独自トークン等)も検知 + secret_scanning_non_provider_patterns: enabled + # 脆弱性の非公開報告(private vulnerability reporting)受付を有効化 + private_vulnerability_reporting: enabled + +# 新規 repo の default configuration に本 configuration を指定(all = public/private 問わず) +default_for_new_repos: all +# 既存 repo 全件に attach(all = org 内すべて) +attach_existing_repos: all + +actions: + # GITHUB_TOKEN の既定権限を read に最小化(書き込みは workflow 側で明示 grant) + default_workflow_permissions: read + # workflow から PR レビュー承認を不可に(権限昇格経路を塞ぐ) + can_approve_pull_request_reviews: false diff --git a/README.md b/README.md index 9506019..744cdef 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,14 @@ Nix flake × devbox。言語非依存でツールを PATH へ配布し、`flake. flake.nix # packages / checks (test・lint) / formatter を公開 devbox.json # 開発環境(Nix の test/lint ツール)と自己 dogfooding .env.template # env-init 用テンプレートの書式例(本 repo の dogfood でも使用) +.github/ghas.yml # ghas-setup 用ポリシー(本 repo の dogfood 兼・書式例) pkgs/ - env-init/ - package.nix # env-init を makeWrapper で wrap する派生 - env-init # エンジン本体(単体実行可能な生スクリプト) - tests/ # bats テスト + env-init/ # 各 pkg の構成は pkg 内 README を参照 + ghas-setup/ ``` +各パッケージの内部構成は [ツール](#ツール) の各 README を参照。 + ## 開発 devbox 経由で開発する。 @@ -52,31 +53,11 @@ SemVer を上げて release する。CLI 契約を変えない依存更新は ** ## ツール -### env-init - -現在の git worktree 用に `.env` を生成する汎用エンジン。worktree 番号 N を計算し、リポジトリルートの -`.env.template`(bash として 1 回評価される)から `.env` を書き出す。プロジェクト非依存設計で、実行時依存は -`bash` / `git` / `gawk` / `gnused` + coreutils。 - -**利用側 repo での使い方**: - -1. `devbox.json` の `packages` に flake 参照を足し、init_hook で起動する。 - - ```jsonc - { - "packages": ["github:airs/devtools/#env-init"], - "shell": { "init_hook": ["[ -f .env ] || env-init"] } - } - ``` - - `` は次の 2 パターンから選ぶ。 - - - 低摩擦(メジャー追従): `github:airs/devtools/v1#env-init` — 移動タグ。`devbox update` で最新の 1.x を取得する。 - - 厳密 pin: `github:airs/devtools/v1.2.0#env-init` — 不変タグ。版を固定し、利用側 Renovate でメジャー更新を抑止できる。 +各ツールの使い方・前提・設定書式は各パッケージの README を参照。 - 本リポジトリ自身の `devbox.json` は、ローカル flake を dogfood するため `path:.#env-init` を使う点だけが利用側と - 異なる。Nix を使わない環境では `pkgs/env-init/env-init` を直接実行できる(生スクリプトは単体実行可能なまま)。 +- [env-init](pkgs/env-init/README.md) — 現在の git worktree 用に `.env` を生成する汎用エンジン。 +- [ghas-setup](pkgs/ghas-setup/README.md) — GitHub org の GHAS / セキュリティ設定を `gh api` で一括適用する汎用エンジン。 -2. **利用側 repo がルートに `.env.template` を用意する**(中央には持ち込まない repo 固有ファイル)。各 worktree で - `N` を参照し、ポートを `$((BASE + N))` でずらし、secret を `openssl rand` / `op read` 等で書く。書式は本リポジトリ - ルートの [`.env.template`](.env.template) を参照(コピー用ではなく書式例)。 +利用側は `devbox.json` の `packages` に flake 参照(`github:airs/devtools/#`)を足す。`` の選び方は +[バージョニング](#バージョニング)を参照。本リポジトリ自身は dogfood のため `path:.#` を使う。Nix を使わない環境では +`pkgs//` を直接実行できる(生スクリプトは単体実行可能なまま)。 diff --git a/devbox.json b/devbox.json index c79b8aa..3b3095c 100644 --- a/devbox.json +++ b/devbox.json @@ -8,7 +8,10 @@ "deadnix@1", "nixfmt@1", "bats@1", - "path:.#env-init" + "gh@2", + "yq-go@4", + "path:.#env-init", + "path:.#ghas-setup" ], "env": { "NIX_CONFIG": "experimental-features = nix-command flakes" diff --git a/flake.nix b/flake.nix index a93538d..87307a8 100644 --- a/flake.nix +++ b/flake.nix @@ -20,6 +20,7 @@ # 複数ツールが入るため default は設けない。利用側は常に `#` を明示する。 packages = forAllSystems (pkgs: { env-init = pkgs.callPackage ./pkgs/env-init/package.nix { }; + ghas-setup = pkgs.callPackage ./pkgs/ghas-setup/package.nix { }; }); # test / lint の単一ソース。`nix flake check` で全て走る。 @@ -31,9 +32,10 @@ { # パッケージ build が通ること。 env-init = self.packages.${system}.env-init; + ghas-setup = self.packages.${system}.ghas-setup; shellcheck = pkgs.runCommand "shellcheck" { nativeBuildInputs = [ pkgs.shellcheck ]; } '' - shellcheck ${./pkgs/env-init/env-init} + shellcheck ${./pkgs/env-init/env-init} ${./pkgs/ghas-setup/ghas-setup} touch "$out" ''; @@ -58,6 +60,27 @@ touch "$out" ''; + # ghas-setup を PATH に載せて bats を実行(gh api を叩く本適用パスは認証が要るため + # 検証せず、引数パース・pre-flight・--dry-run のみ。gh は不要)。 + ghas-setup-bats = + pkgs.runCommand "ghas-setup-bats" + { + nativeBuildInputs = [ + pkgs.bats + pkgs.bash + pkgs.git + pkgs.coreutils + pkgs.yq-go + self.packages.${system}.ghas-setup + ]; + } + '' + cp -r ${./pkgs/ghas-setup/tests} tests + export HOME="$TMPDIR" + bats tests + touch "$out" + ''; + statix = pkgs.runCommand "statix-check" { nativeBuildInputs = [ pkgs.statix ]; } '' statix check ${./.} touch "$out" @@ -69,7 +92,7 @@ ''; nixfmt = pkgs.runCommand "nixfmt-check" { nativeBuildInputs = [ pkgs.nixfmt ]; } '' - nixfmt --check ${./flake.nix} ${./pkgs/env-init/package.nix} + nixfmt --check ${./flake.nix} ${./pkgs/env-init/package.nix} ${./pkgs/ghas-setup/package.nix} touch "$out" ''; } diff --git a/pkgs/env-init/README.md b/pkgs/env-init/README.md new file mode 100644 index 0000000..b3da366 --- /dev/null +++ b/pkgs/env-init/README.md @@ -0,0 +1,33 @@ +# env-init + +現在の git worktree 用に `.env` を生成する汎用エンジン。worktree 番号 N を計算し、リポジトリルートの +`.env.template`(bash として 1 回評価される)から `.env` を書き出す。プロジェクト非依存設計で、実行時依存は +`bash` / `git` / `gawk` / `gnused` + coreutils。 + +## 構成 + +``` +package.nix # env-init を makeWrapper で wrap する派生 +env-init # エンジン本体(単体実行可能な生スクリプト) +tests/ # bats テスト +``` + +## 利用側 repo での使い方 + +1. `devbox.json` の `packages` に flake 参照を足し、init_hook で起動する。 + + ```jsonc + { + "packages": ["github:airs/devtools/#env-init"], + "shell": { "init_hook": ["[ -f .env ] || env-init"] } + } + ``` + + `` の選び方(メジャー追従/厳密 pin)はルート [README の「バージョニング」](../../README.md#バージョニング) を参照。 + + 本リポジトリ自身の `devbox.json` は、ローカル flake を dogfood するため `path:.#env-init` を使う点だけが利用側と + 異なる。Nix を使わない環境では `pkgs/env-init/env-init` を直接実行できる(生スクリプトは単体実行可能なまま)。 + +2. **利用側 repo がルートに `.env.template` を用意する**(中央には持ち込まない repo 固有ファイル)。各 worktree で + `N` を参照し、ポートを `$((BASE + N))` でずらし、secret を `openssl rand` / `op read` 等で書く。書式は本リポジトリ + ルートの [`.env.template`](../../.env.template) を参照(コピー用ではなく書式例)。 diff --git a/pkgs/ghas-setup/README.md b/pkgs/ghas-setup/README.md new file mode 100644 index 0000000..b0fa309 --- /dev/null +++ b/pkgs/ghas-setup/README.md @@ -0,0 +1,62 @@ +# ghas-setup + +GitHub **org 単位**で GHAS / Actions セキュリティ設定を `gh api`(code-security-configurations)で一括適用する汎用エンジン。 +適用対象は repo 単位ではなく org 全体で、`default_for_new_repos` / `attach_existing_repos` により新規・既存 repo の両方へ反映される。 +engine(本スクリプト)はロジックだけを持ち、org 名・scanner・enforcement・Actions 権限などのポリシー値はすべて config(`.github/ghas.yml`)由来。 + +## 構成 + +``` +package.nix # ghas-setup を makeWrapper で wrap する派生 +ghas-setup # エンジン本体(単体実行可能な生スクリプト) +tests/ # bats テスト +``` + +## 前提 + +- 対象 org で **GitHub Advanced Security (GHAS) が有効**であること。GHAS が有効でないと `advanced_security` 等の scanner 適用が失敗する。 +- gh CLI が認証済みで **`admin:org` scope** を持つこと(org レベル設定の変更に必要)。 + + ```sh + gh auth login + gh auth refresh -h github.com -s admin:org + ``` +- `yq`(mikefarah 版 = yq-go)。本パッケージは PATH に同梱する。 + +## 使い方 + +1. `devbox.json` の `packages` に flake 参照を足す。 + + ```jsonc + { "packages": ["github:airs/devtools/#ghas-setup"] } + ``` + + `` の選び方(メジャー追従/厳密 pin)はルート [README の「バージョニング」](../../README.md#バージョニング) を参照。 + +2. **利用側 repo がルートに `.github/ghas.yml` を用意する**(ポリシーの単一ソース。書式は下記)。 + +3. 適用する。org を変更する前に必ず `--dry-run` で解決値と叩く API を確認する。 + + ```sh + ghas-setup --dry-run # org を変更せず適用予定を表示 + ghas-setup # .github/ghas.yml を org へ適用 + ghas-setup --config # 別の yaml を渡す(複数 org 用) + ``` + + config は呼び出した git worktree のルートの `.github/ghas.yml`(cwd 基準で解決)。`--config` で上書きできる。 + +## config(`.github/ghas.yml`) + +書式は本リポジトリ実物の [`.github/ghas.yml`](../../.github/ghas.yml) を参照(コピー用の書式例)。利用側はこれをコピーして自 repo の +`.github/ghas.yml` に置き、`org` を自分の org 名に変える。各項目の意味は +[code security configurations の REST API ドキュメント](https://docs.github.com/en/rest/code-security/configurations) を参照。 + +主な項目: + +- `org` — 適用対象の org 名。 +- `configuration.name` / `description` / `enforcement` — code security configuration の識別名・説明・強制方法(`enforced` で repo 側のオーバーライドを禁止)。 +- `configuration.scanners` — 有効化する scanner(`advanced_security` / `dependency_graph` / `dependabot_alerts` / `secret_scanning` ほか)と値。 +- `default_for_new_repos` — 新規 repo の default configuration に指定する範囲(`all` = public/private 問わず)。 +- `attach_existing_repos` — 既存 repo へ attach する範囲(`all` = org 内すべて)。 +- `actions.default_workflow_permissions` — `GITHUB_TOKEN` の既定権限(`read` で最小化)。 +- `actions.can_approve_pull_request_reviews` — workflow から PR レビュー承認を許すか(`false` で権限昇格経路を塞ぐ)。 diff --git a/pkgs/ghas-setup/ghas-setup b/pkgs/ghas-setup/ghas-setup new file mode 100755 index 0000000..60be1b8 --- /dev/null +++ b/pkgs/ghas-setup/ghas-setup @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +# GitHub org の GHAS / Actions セキュリティ設定を code-security-configurations API で一括有効化する汎用エンジン。 +# engine(本スクリプト)とポリシー値(config yaml)を分離する。 +# engine: config を読む / gh api を叩く / 適用後の状態を読み戻して表示する だけを知る。 +# org 名・scanner 名・enable 値・enforcement・Actions 設定はすべて config 由来。 +# config は呼び出した git worktree の .github/ghas.yml(--config で別 yaml を渡せる)。 +# 詳細は README.md を参照。 +set -euo pipefail + +# リポジトリルート(このスクリプトを呼んだ git worktree のルート)を cwd 基準で解決する。 +# git 管理外で実行された場合は git の生エラーではなく、何をすべきか分かるメッセージで終了する。 +if ! REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null); then + echo "git worktree 内で実行してください(git リポジトリのルートから .github/ghas.yml を解決します)。" >&2 + exit 1 +fi + +CONFIG="$REPO_ROOT/.github/ghas.yml" +DRY_RUN=false + +# 引数パース: --config (別 org 用 yaml を渡す)/ --dry-run(org を変更せず適用予定を表示) +while [ $# -gt 0 ]; do + case "$1" in + --config) + CONFIG="$2"; shift 2 ;; + --config=*) + CONFIG="${1#*=}"; shift ;; + --dry-run) + DRY_RUN=true; shift ;; + *) + echo "不明な引数: $1" >&2 + echo "usage: ghas-setup [--config ] [--dry-run]" >&2 + exit 1 ;; + esac +done + +# pre-flight: yq(mikefarah 版 = devbox の yq-go) +command -v yq >/dev/null 2>&1 || { + echo "yq が見つかりません。'devbox shell' 内で実行してください(devbox.json の yq-go)。" >&2 + exit 1 +} + +# pre-flight: config file +[ -f "$CONFIG" ] || { + echo "config file が見つかりません: $CONFIG" >&2 + exit 1 +} + +# config 読み出し(ポリシー値の単一ソース) +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") + +# configuration の body フィールドを config 由来で動的生成 +CONFIG_BODY_FIELDS=( + -f "name=$CONFIG_NAME" + -f "description=$CONFIG_DESC" + -f "enforcement=$ENFORCEMENT" +) +while IFS= read -r entry; do + CONFIG_BODY_FIELDS+=(-f "$entry") +done < <(yq '.configuration.scanners | to_entries | .[] | .key + "=" + .value' "$CONFIG") + +# --dry-run: 解決済みの値とこれから叩く API を表示して org を変更せず exit +if [ "$DRY_RUN" = true ]; then + echo "==> dry-run: config=$CONFIG" + echo "org: $ORG" + echo "configuration:" + echo " name: $CONFIG_NAME" + echo " enforcement: $ENFORCEMENT" + echo " scanners:" + yq '.configuration.scanners | to_entries | .[] | " " + .key + ": " + .value' "$CONFIG" + echo "default_for_new_repos: $DEFAULT_FOR_NEW_REPOS" + echo "attach_existing_repos: $ATTACH_SCOPE" + echo "actions.default_workflow_permissions: $ACTIONS_DEFAULT_PERMS" + echo "actions.can_approve_pull_request_reviews: $ACTIONS_APPROVE_PR" + echo + echo "==> これから叩く API:" + echo " POST|PATCH /orgs/$ORG/code-security/configurations (name / description / enforcement / scanners)" + echo " PUT /orgs/$ORG/code-security/configurations//defaults default_for_new_repos=$DEFAULT_FOR_NEW_REPOS" + echo " POST /orgs/$ORG/code-security/configurations//attach scope=$ATTACH_SCOPE" + echo " PUT /orgs/$ORG/actions/permissions/workflow default_workflow_permissions=$ACTIONS_DEFAULT_PERMS can_approve_pull_request_reviews=$ACTIONS_APPROVE_PR" + exit 0 +fi + +# 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 +} + +echo "==> $ORG org の code security configuration を有効化します" + +# 既存 configuration を name で lookup(冪等化のため) +CONFIG_ID=$(gh api "/orgs/$ORG/code-security/configurations" \ + --jq ".[] | select(.name == \"$CONFIG_NAME\") | .id" | head -n1) + +if [ -z "$CONFIG_ID" ]; then + echo " - configuration を新規作成: $CONFIG_NAME" + CONFIG_ID=$(gh api --method POST "/orgs/$ORG/code-security/configurations" \ + "${CONFIG_BODY_FIELDS[@]}" --jq '.id') +else + echo " - 既存 configuration を更新: $CONFIG_NAME (id=$CONFIG_ID)" + gh api --method PATCH "/orgs/$ORG/code-security/configurations/$CONFIG_ID" \ + "${CONFIG_BODY_FIELDS[@]}" --silent +fi + +# 新規 repo の default に指定 +echo "==> 新規 repo の default configuration に指定" +gh api --method PUT "/orgs/$ORG/code-security/configurations/$CONFIG_ID/defaults" \ + -f "default_for_new_repos=$DEFAULT_FOR_NEW_REPOS" --silent + +# 既存 repo に attach +echo "==> 既存 repo 全件に attach" +gh api --method POST "/orgs/$ORG/code-security/configurations/$CONFIG_ID/attach" \ + -f "scope=$ATTACH_SCOPE" --silent + +# Actions: GITHUB_TOKEN default permission を config 値に +echo "==> Actions GITHUB_TOKEN default permission = $ACTIONS_DEFAULT_PERMS" +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 + +# 検証: 適用後の状態を読み戻して表示 +echo "==> 適用後の状態:" +echo "--- configuration ---" +gh api "/orgs/$ORG/code-security/configurations/$CONFIG_ID" --jq '{ + id, name, enforcement, + advanced_security, dependency_graph, + dependabot_alerts, dependabot_security_updates, + secret_scanning, secret_scanning_push_protection, + secret_scanning_validity_checks, secret_scanning_non_provider_patterns, + private_vulnerability_reporting +}' +echo "--- default for new repos ---" +gh api "/orgs/$ORG/code-security/configurations/defaults" --jq '.[] | {default_for_new_repos, configuration_name: .configuration.name}' +echo "--- Actions permissions ---" +gh api "/orgs/$ORG/actions/permissions/workflow" + +echo "完了しました。" diff --git a/pkgs/ghas-setup/package.nix b/pkgs/ghas-setup/package.nix new file mode 100644 index 0000000..850fb68 --- /dev/null +++ b/pkgs/ghas-setup/package.nix @@ -0,0 +1,39 @@ +# ghas-setup を makeWrapper で wrap する。生スクリプト (./ghas-setup) は libexec にそのまま置き、 +# 外側の wrapper で runtimeInputs を PATH 先頭に prefix して exec するだけ(中身は無改変)。 +# 設計の理由は pkgs/env-init/package.nix のコメントを参照(writeShellApplication を避ける理由・ +# shebang を nix store の bash に固定する理由・PATH prefix で既存 PATH を suffix に残す理由は同じ)。 +# +# 同梱する runtimeInputs: スクリプトが使う git (rev-parse) / gh (api) / yq (yq-go, config 読み) / +# coreutils。gh は closure が重いが、利用側の ~/.config/gh 認証状態を読むため動作上の問題はない。 +{ + lib, + runCommand, + makeWrapper, + bashNonInteractive, + coreutils, + git, + gh, + yq-go, +}: +runCommand "ghas-setup" + { + nativeBuildInputs = [ makeWrapper ]; + meta = { + description = "GitHub org の GHAS / セキュリティ設定を gh api で一括適用する汎用エンジン"; + mainProgram = "ghas-setup"; + }; + } + '' + install -Dm755 ${./ghas-setup} "$out/libexec/ghas-setup" + substituteInPlace "$out/libexec/ghas-setup" \ + --replace-fail '#!/usr/bin/env bash' '#!${bashNonInteractive}/bin/bash' + makeWrapper "$out/libexec/ghas-setup" "$out/bin/ghas-setup" \ + --prefix PATH : ${ + lib.makeBinPath [ + coreutils + git + gh + yq-go + ] + } + '' diff --git a/pkgs/ghas-setup/tests/ghas-setup.bats b/pkgs/ghas-setup/tests/ghas-setup.bats new file mode 100644 index 0000000..06763b7 --- /dev/null +++ b/pkgs/ghas-setup/tests/ghas-setup.bats @@ -0,0 +1,89 @@ +#!/usr/bin/env bats +# ghas-setup のテスト。ghas-setup は PATH 上にある前提(GHAS_SETUP で上書き可)。 +# 実 org を変更するパス(gh api)は認証が要るため検証しない。認証なしで到達できる +# 引数パース・pre-flight・--dry-run(gh auth チェックより手前で exit)だけを対象にする。 + +GHAS_SETUP="${GHAS_SETUP:-ghas-setup}" + +setup() { + BASE="$BATS_TEST_TMPDIR" + REPO="$BASE/repo" + mkdir -p "$REPO" + cd "$REPO" + git init -q -b main + git config user.email test@example.com + git config user.name test + git commit -q --allow-empty -m init +} + +# .github/ghas.yml を書く(dry-run が読む全項目を含む)。 +write_config() { + mkdir -p "$1/.github" + cat > "$1/.github/ghas.yml" <<'EOF' +org: example-org +configuration: + name: example-default + description: Test configuration. + enforcement: enforced + scanners: + advanced_security: enabled + secret_scanning: enabled +default_for_new_repos: all +attach_existing_repos: all +actions: + default_workflow_permissions: read + can_approve_pull_request_reviews: false +EOF +} + +@test "不明な引数は usage を出して exit 1" { + write_config "$REPO" + run "$GHAS_SETUP" --nope + [ "$status" -eq 1 ] + [[ "$output" == *"usage: ghas-setup"* ]] +} + +@test "git 管理外で実行すると明確なエラーで exit 1" { + mkdir -p "$BASE/nongit" + cd "$BASE/nongit" + run "$GHAS_SETUP" --dry-run + [ "$status" -eq 1 ] + [[ "$output" == *"git worktree"* ]] +} + +@test "config が無ければ exit 1" { + run "$GHAS_SETUP" --dry-run + [ "$status" -eq 1 ] + [[ "$output" == *"config file が見つかりません"* ]] +} + +@test "--dry-run は解決値と叩く API を出して exit 0(gh は呼ばない)" { + write_config "$REPO" + run "$GHAS_SETUP" --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"org: example-org"* ]] + [[ "$output" == *"name: example-default"* ]] + [[ "$output" == *"これから叩く API"* ]] + [[ "$output" == *"/orgs/example-org/actions/permissions/workflow"* ]] +} + +@test "--config で別 yaml を渡せる" { + mkdir -p "$BASE/alt" + cat > "$BASE/alt/other.yml" <<'EOF' +org: alt-org +configuration: + name: alt-default + description: Alt. + enforcement: unenforced + scanners: + advanced_security: enabled +default_for_new_repos: none +attach_existing_repos: none +actions: + default_workflow_permissions: read + can_approve_pull_request_reviews: false +EOF + run "$GHAS_SETUP" --config "$BASE/alt/other.yml" --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"org: alt-org"* ]] +} From 5a964b42d18b8b163112cb1138c650413ac3e915 Mon Sep 17 00:00:00 2001 From: Masakuni Kato <7091+mackato@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:53:52 +0900 Subject: [PATCH 2/8] =?UTF-8?q?Copilot=20=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E6=8C=87=E6=91=98=E3=81=AB=E5=AF=BE=E5=BF=9C=EF=BC=88?= =?UTF-8?q?=E5=BC=95=E6=95=B0=E3=82=AC=E3=83=BC=E3=83=89=E3=83=BBjq=20?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=B8=E3=82=A7=E3=82=AF=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=83=BB=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - --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 --- flake.nix | 5 +++-- pkgs/ghas-setup/ghas-setup | 12 +++++++++--- pkgs/ghas-setup/tests/ghas-setup.bats | 7 +++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/flake.nix b/flake.nix index 87307a8..325468f 100644 --- a/flake.nix +++ b/flake.nix @@ -60,8 +60,9 @@ touch "$out" ''; - # ghas-setup を PATH に載せて bats を実行(gh api を叩く本適用パスは認証が要るため - # 検証せず、引数パース・pre-flight・--dry-run のみ。gh は不要)。 + # ghas-setup を PATH に載せて bats を実行。テストは引数パース・pre-flight・--dry-run + # のみで gh api を呼ばない(gh auth チェックより手前で exit する)。wrap 済みパッケージを + # 使うため closure には gh が入るが、テスト実行時に起動はしない。 ghas-setup-bats = pkgs.runCommand "ghas-setup-bats" { diff --git a/pkgs/ghas-setup/ghas-setup b/pkgs/ghas-setup/ghas-setup index 60be1b8..bfe0d6c 100755 --- a/pkgs/ghas-setup/ghas-setup +++ b/pkgs/ghas-setup/ghas-setup @@ -21,6 +21,11 @@ DRY_RUN=false while [ $# -gt 0 ]; do case "$1" in --config) + if [ $# -lt 2 ]; then + echo "--config に値がありません" >&2 + echo "usage: ghas-setup [--config ] [--dry-run]" >&2 + exit 1 + fi CONFIG="$2"; shift 2 ;; --config=*) CONFIG="${1#*=}"; shift ;; @@ -101,9 +106,10 @@ gh auth status -h github.com 2>&1 | grep -q "admin:org" || { echo "==> $ORG org の code security configuration を有効化します" -# 既存 configuration を name で lookup(冪等化のため) -CONFIG_ID=$(gh api "/orgs/$ORG/code-security/configurations" \ - --jq ".[] | select(.name == \"$CONFIG_NAME\") | .id" | head -n1) +# 既存 configuration を name で lookup(冪等化のため)。name は config 由来の文字列なので +# jq 式に直埋めせず env 経由で渡す(" や \ を含んでも jq 式が壊れない)。 +CONFIG_ID=$(GHAS_CONFIG_NAME="$CONFIG_NAME" gh api "/orgs/$ORG/code-security/configurations" \ + --jq '.[] | select(.name == env.GHAS_CONFIG_NAME) | .id' | head -n1) if [ -z "$CONFIG_ID" ]; then echo " - configuration を新規作成: $CONFIG_NAME" diff --git a/pkgs/ghas-setup/tests/ghas-setup.bats b/pkgs/ghas-setup/tests/ghas-setup.bats index 06763b7..ad2f8e3 100644 --- a/pkgs/ghas-setup/tests/ghas-setup.bats +++ b/pkgs/ghas-setup/tests/ghas-setup.bats @@ -43,6 +43,13 @@ EOF [[ "$output" == *"usage: ghas-setup"* ]] } +@test "--config に値が無いと usage を出して exit 1" { + write_config "$REPO" + run "$GHAS_SETUP" --config + [ "$status" -eq 1 ] + [[ "$output" == *"--config に値がありません"* ]] +} + @test "git 管理外で実行すると明確なエラーで exit 1" { mkdir -p "$BASE/nongit" cd "$BASE/nongit" From 805aca3969555c4916fbb7616d6d886dec333248 Mon Sep 17 00:00:00 2001 From: Masakuni Kato <7091+mackato@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:01:48 +0900 Subject: [PATCH 3/8] =?UTF-8?q?Copilot=20=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E7=AC=AC2=E5=BC=BE=E3=81=AB=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=EF=BC=88configuration=20lookup=20=E3=81=AE=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=83=B3=E3=82=B0=E3=83=BBattach=20=E3=83=AD=E3=82=B0?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 既存 configuration の lookup に --paginate を追加し、org 内の configuration が 30 件を超えても全件から検索して重複作成を防ぐ - 既存 repo への attach ログを実際の scope を含む表現に修正 (attach_existing_repos: none 等で「全件」と誤表示しない) Co-Authored-By: Claude Opus 4.8 --- pkgs/ghas-setup/ghas-setup | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/ghas-setup/ghas-setup b/pkgs/ghas-setup/ghas-setup index bfe0d6c..f3799a9 100755 --- a/pkgs/ghas-setup/ghas-setup +++ b/pkgs/ghas-setup/ghas-setup @@ -108,7 +108,7 @@ echo "==> $ORG org の code security configuration を有効化します" # 既存 configuration を name で lookup(冪等化のため)。name は config 由来の文字列なので # jq 式に直埋めせず env 経由で渡す(" や \ を含んでも jq 式が壊れない)。 -CONFIG_ID=$(GHAS_CONFIG_NAME="$CONFIG_NAME" gh api "/orgs/$ORG/code-security/configurations" \ +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) if [ -z "$CONFIG_ID" ]; then @@ -127,7 +127,7 @@ gh api --method PUT "/orgs/$ORG/code-security/configurations/$CONFIG_ID/defaults -f "default_for_new_repos=$DEFAULT_FOR_NEW_REPOS" --silent # 既存 repo に attach -echo "==> 既存 repo 全件に attach" +echo "==> 既存 repo に attach (scope=$ATTACH_SCOPE)" gh api --method POST "/orgs/$ORG/code-security/configurations/$CONFIG_ID/attach" \ -f "scope=$ATTACH_SCOPE" --silent From d351758fa90e241bb51c613c65ef8ea50dce1917 Mon Sep 17 00:00:00 2001 From: Masakuni Kato <7091+mackato@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:08:30 +0900 Subject: [PATCH 4/8] =?UTF-8?q?package.nix=20=E3=81=AE=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=82=92=E6=AD=A3=E7=A2=BA=E5=8C=96=EF=BC=88?= =?UTF-8?q?=E4=B8=AD=E8=BA=AB=E3=81=AF=E7=84=A1=E6=94=B9=E5=A4=89=E2=86=92?= =?UTF-8?q?=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF=E3=81=AF=E7=84=A1=E6=94=B9?= =?UTF-8?q?=E5=A4=89=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shebang を substituteInPlace で差し替えるため「中身は無改変」は厳密には 不正確。shebang のみ差し替える旨を明記する(Copilot レビュー指摘)。 Co-Authored-By: Claude Opus 4.8 --- pkgs/ghas-setup/package.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/ghas-setup/package.nix b/pkgs/ghas-setup/package.nix index 850fb68..44a4fab 100644 --- a/pkgs/ghas-setup/package.nix +++ b/pkgs/ghas-setup/package.nix @@ -1,5 +1,6 @@ # ghas-setup を makeWrapper で wrap する。生スクリプト (./ghas-setup) は libexec にそのまま置き、 -# 外側の wrapper で runtimeInputs を PATH 先頭に prefix して exec するだけ(中身は無改変)。 +# 外側の wrapper で runtimeInputs を PATH 先頭に prefix して exec するだけ(ロジックは無改変。 +# shebang のみビルド時に絶対 bash へ差し替える=下記 substituteInPlace)。 # 設計の理由は pkgs/env-init/package.nix のコメントを参照(writeShellApplication を避ける理由・ # shebang を nix store の bash に固定する理由・PATH prefix で既存 PATH を suffix に残す理由は同じ)。 # From 00793f0ac9240e43cfe4724709b55d51adc49692 Mon Sep 17 00:00:00 2001 From: Masakuni Kato <7091+mackato@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:16:52 +0900 Subject: [PATCH 5/8] =?UTF-8?q?Copilot=20=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E7=AC=AC4=E5=BC=BE=E3=81=AB=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=EF=BC=88lookup=20=E3=81=AE=20SIGPIPE=20=E5=9B=9E=E9=81=BF?= =?UTF-8?q?=E3=83=BBgrep=20=E5=90=8C=E6=A2=B1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 既存 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 --- pkgs/ghas-setup/ghas-setup | 11 +++++++---- pkgs/ghas-setup/package.nix | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pkgs/ghas-setup/ghas-setup b/pkgs/ghas-setup/ghas-setup index f3799a9..caf5dee 100755 --- a/pkgs/ghas-setup/ghas-setup +++ b/pkgs/ghas-setup/ghas-setup @@ -106,10 +106,13 @@ gh auth status -h github.com 2>&1 | grep -q "admin:org" || { echo "==> $ORG org の code security configuration を有効化します" -# 既存 configuration を name で lookup(冪等化のため)。name は config 由来の文字列なので -# jq 式に直埋めせず env 経由で渡す(" や \ を含んでも jq 式が壊れない)。 -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) +# 既存 configuration を name で lookup(冪等化のため)。configuration が 30 件(既定 per_page)を +# 超えても取りこぼさないよう --paginate で全件取得する。--slurp で全ページを 1 つの配列にまとめ、 +# jq 側で最初の一致 id を取り出す(`| head -n1` だと pagination 途中で stdout が閉じ、gh が SIGPIPE +# で非 0 終了して set -o pipefail がスクリプト全体を落としうるため使わない)。 +# name は config 由来の文字列なので jq 式に直埋めせず env 経由で渡す(" や \ を含んでも壊れない)。 +CONFIG_ID=$(GHAS_CONFIG_NAME="$CONFIG_NAME" gh api --paginate --slurp "/orgs/$ORG/code-security/configurations" \ + --jq '[.[][] | select(.name == env.GHAS_CONFIG_NAME) | .id] | .[0] // ""') if [ -z "$CONFIG_ID" ]; then echo " - configuration を新規作成: $CONFIG_NAME" diff --git a/pkgs/ghas-setup/package.nix b/pkgs/ghas-setup/package.nix index 44a4fab..a8a62dc 100644 --- a/pkgs/ghas-setup/package.nix +++ b/pkgs/ghas-setup/package.nix @@ -5,7 +5,8 @@ # shebang を nix store の bash に固定する理由・PATH prefix で既存 PATH を suffix に残す理由は同じ)。 # # 同梱する runtimeInputs: スクリプトが使う git (rev-parse) / gh (api) / yq (yq-go, config 読み) / -# coreutils。gh は closure が重いが、利用側の ~/.config/gh 認証状態を読むため動作上の問題はない。 +# gnugrep (admin:org scope 判定の grep。coreutils には grep が入らない) / coreutils。 +# gh は closure が重いが、利用側の ~/.config/gh 認証状態を読むため動作上の問題はない。 { lib, runCommand, @@ -14,6 +15,7 @@ coreutils, git, gh, + gnugrep, yq-go, }: runCommand "ghas-setup" @@ -34,6 +36,7 @@ runCommand "ghas-setup" coreutils git gh + gnugrep yq-go ] } From 92edbfe7404f336c00d76a4a1ac2a8105a371ddc Mon Sep 17 00:00:00 2001 From: Masakuni Kato <7091+mackato@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:26:21 +0900 Subject: [PATCH 6/8] =?UTF-8?q?ghas-setup=20=E3=81=AE=E5=AE=9F=E9=81=A9?= =?UTF-8?q?=E7=94=A8=E3=83=91=E3=82=B9=E3=82=92=20gh=20=E3=82=B9=E3=82=BF?= =?UTF-8?q?=E3=83=96=E3=81=A7=E8=87=AA=E5=8B=95=E3=83=86=E3=82=B9=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 引数パース・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 --- flake.nix | 11 +++-- pkgs/ghas-setup/tests/ghas-setup.bats | 69 +++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/flake.nix b/flake.nix index 325468f..1f1309a 100644 --- a/flake.nix +++ b/flake.nix @@ -60,9 +60,12 @@ touch "$out" ''; - # ghas-setup を PATH に載せて bats を実行。テストは引数パース・pre-flight・--dry-run - # のみで gh api を呼ばない(gh auth チェックより手前で exit する)。wrap 済みパッケージを - # 使うため closure には gh が入るが、テスト実行時に起動はしない。 + # ghas-setup の bats。引数パース・pre-flight・--dry-run は wrap 済みパッケージ + # ($GHAS_SETUP) で、実適用パス(create/update 分岐・API 呼び出し列)は gh をスタブして + # 生スクリプト ($GHAS_SETUP_RAW) を bash で直起動して検証する(wrapper は gh を PATH + # 先頭に prefix するため stub で上書きできない。bash 直起動なら shebang も回避でき、 + # PATH 上の stub gh が使われる)。wrap 済みパッケージの closure には gh が入るが、 + # --dry-run テストでは gh auth チェックより手前で exit するため起動はしない。 ghas-setup-bats = pkgs.runCommand "ghas-setup-bats" { @@ -71,12 +74,14 @@ pkgs.bash pkgs.git pkgs.coreutils + pkgs.gnugrep pkgs.yq-go self.packages.${system}.ghas-setup ]; } '' cp -r ${./pkgs/ghas-setup/tests} tests + export GHAS_SETUP_RAW=${./pkgs/ghas-setup/ghas-setup} export HOME="$TMPDIR" bats tests touch "$out" diff --git a/pkgs/ghas-setup/tests/ghas-setup.bats b/pkgs/ghas-setup/tests/ghas-setup.bats index ad2f8e3..097398e 100644 --- a/pkgs/ghas-setup/tests/ghas-setup.bats +++ b/pkgs/ghas-setup/tests/ghas-setup.bats @@ -1,7 +1,9 @@ #!/usr/bin/env bats -# ghas-setup のテスト。ghas-setup は PATH 上にある前提(GHAS_SETUP で上書き可)。 -# 実 org を変更するパス(gh api)は認証が要るため検証しない。認証なしで到達できる -# 引数パース・pre-flight・--dry-run(gh auth チェックより手前で exit)だけを対象にする。 +# ghas-setup のテスト。引数パース・pre-flight・--dry-run は wrap 済みの ghas-setup(GHAS_SETUP、 +# 既定は PATH 上の ghas-setup)で検証する。実適用パス(create/update 分岐・gh api 呼び出し列)は +# gh をスタブし、生スクリプト(GHAS_SETUP_RAW)を bash で直起動して検証する。wrapper は gh を +# PATH 先頭に prefix するため stub で上書きできないが、bash 直起動なら shebang を回避でき PATH 上の +# stub gh が使われる。 GHAS_SETUP="${GHAS_SETUP:-ghas-setup}" @@ -36,6 +38,36 @@ actions: EOF } +# gh をスタブする。呼び出し引数を $GH_LOG に記録し、種別ごとに想定 stdout を返す: +# - auth status : "admin:org" を出力(scope 判定の grep 用)/ exit 0 +# - api(--slurp あり) : 既存 lookup。$1(""=新規, それ以外=既存 id)を返す +# - api(POST で末尾が /configurations): 新規作成。固定 id 123 を返す +# - その他 api : 読み戻し等。{} を返す +make_gh_stub() { + STUB_DIR="$BASE/stubbin" + GH_LOG="$BASE/gh-calls.log" + mkdir -p "$STUB_DIR" + : > "$GH_LOG" + { + printf '#!%s\n' "$(command -v bash)" + cat <> "$GH_LOG" +case "\$1" in + auth) echo "admin:org"; exit 0 ;; + api) + for a in "\$@"; do [ "\$a" = "--slurp" ] && { printf '%s' "$1"; exit 0; }; done + case "\$*" in + *"--method POST"*"/code-security/configurations "*) printf '123'; exit 0 ;; + esac + echo '{}'; exit 0 ;; +esac +exit 0 +EOF + } > "$STUB_DIR/gh" + chmod +x "$STUB_DIR/gh" + PATH="$STUB_DIR:$PATH" +} + @test "不明な引数は usage を出して exit 1" { write_config "$REPO" run "$GHAS_SETUP" --nope @@ -94,3 +126,34 @@ EOF [ "$status" -eq 0 ] [[ "$output" == *"org: alt-org"* ]] } + +@test "適用(新規): 既存 config 無し → POST 作成 + defaults/attach/actions を config 値で叩く" { + write_config "$REPO" + make_gh_stub "" # lookup は空 → 新規作成分岐 + run bash "$GHAS_SETUP_RAW" + [ "$status" -eq 0 ] + [[ "$output" == *"configuration を新規作成"* ]] + # 新規作成 POST と、作成された id=123 への defaults/attach/actions 適用 + grep -q -- "--method POST /orgs/example-org/code-security/configurations " "$GH_LOG" + grep -q -- "/code-security/configurations/123/defaults" "$GH_LOG" + grep -q -- "/code-security/configurations/123/attach" "$GH_LOG" + grep -q -- "--method PUT /orgs/example-org/actions/permissions/workflow" "$GH_LOG" + # config 値が field として渡る + grep -q -- "default_for_new_repos=all" "$GH_LOG" + grep -q -- "scope=all" "$GH_LOG" + grep -q -- "can_approve_pull_request_reviews=false" "$GH_LOG" + # 更新分岐は呼ばれない + ! grep -q -- "--method PATCH" "$GH_LOG" +} + +@test "適用(更新): 既存 config 有り → PATCH 更新し新規作成しない" { + write_config "$REPO" + make_gh_stub "999" # lookup が既存 id を返す → 更新分岐 + run bash "$GHAS_SETUP_RAW" + [ "$status" -eq 0 ] + [[ "$output" == *"既存 configuration を更新"* ]] + grep -q -- "--method PATCH /orgs/example-org/code-security/configurations/999" "$GH_LOG" + grep -q -- "/code-security/configurations/999/defaults" "$GH_LOG" + # 新規作成 POST は叩かない + ! grep -q -- "--method POST /orgs/example-org/code-security/configurations " "$GH_LOG" +} From d9bdb4dbc748263ff54f64e53678de9e03568ca6 Mon Sep 17 00:00:00 2001 From: Masakuni Kato <7091+mackato@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:27:24 +0900 Subject: [PATCH 7/8] =?UTF-8?q?dry-run=20=E3=81=AE=20API=20=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=82=92=20create/update=20=E3=81=A7=E5=88=86?= =?UTF-8?q?=E3=81=91=E3=80=81bats=20=E3=81=AB=20raw=20=E3=82=B9=E3=82=AF?= =?UTF-8?q?=E3=83=AA=E3=83=97=E3=83=88=E6=97=A2=E5=AE=9A=E5=80=A4=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - --dry-run の「これから叩く API」が POST|PATCH を 1 行にまとめており、 更新時の PATCH .../ と作成時の POST を取り違えやすかったため分けて表示 - bats の GHAS_SETUP_RAW に未設定時の既定(隣の生スクリプト)を入れ、 repo で bats を直接実行しても実適用パステストが動くようにする Co-Authored-By: Claude Opus 4.8 --- pkgs/ghas-setup/ghas-setup | 3 ++- pkgs/ghas-setup/tests/ghas-setup.bats | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/ghas-setup/ghas-setup b/pkgs/ghas-setup/ghas-setup index caf5dee..8b9aaaf 100755 --- a/pkgs/ghas-setup/ghas-setup +++ b/pkgs/ghas-setup/ghas-setup @@ -85,7 +85,8 @@ if [ "$DRY_RUN" = true ]; then echo "actions.can_approve_pull_request_reviews: $ACTIONS_APPROVE_PR" echo echo "==> これから叩く API:" - echo " POST|PATCH /orgs/$ORG/code-security/configurations (name / description / enforcement / scanners)" + echo " POST /orgs/$ORG/code-security/configurations (既存が無ければ新規作成)" + echo " PATCH /orgs/$ORG/code-security/configurations/ (既存があれば更新) name / description / enforcement / scanners" echo " PUT /orgs/$ORG/code-security/configurations//defaults default_for_new_repos=$DEFAULT_FOR_NEW_REPOS" echo " POST /orgs/$ORG/code-security/configurations//attach scope=$ATTACH_SCOPE" echo " PUT /orgs/$ORG/actions/permissions/workflow default_workflow_permissions=$ACTIONS_DEFAULT_PERMS can_approve_pull_request_reviews=$ACTIONS_APPROVE_PR" diff --git a/pkgs/ghas-setup/tests/ghas-setup.bats b/pkgs/ghas-setup/tests/ghas-setup.bats index 097398e..3f20e45 100644 --- a/pkgs/ghas-setup/tests/ghas-setup.bats +++ b/pkgs/ghas-setup/tests/ghas-setup.bats @@ -6,6 +6,9 @@ # stub gh が使われる。 GHAS_SETUP="${GHAS_SETUP:-ghas-setup}" +# 実適用パス用の生スクリプト。flake check では nix store のパスが渡る。未設定時(repo で +# `bats pkgs/ghas-setup/tests` を直接実行)は隣の生スクリプトを既定にする。 +GHAS_SETUP_RAW="${GHAS_SETUP_RAW:-$BATS_TEST_DIRNAME/../ghas-setup}" setup() { BASE="$BATS_TEST_TMPDIR" From 1e5510a40071fa577a7e91cad3f3df0f5c6e7226 Mon Sep 17 00:00:00 2001 From: Masakuni Kato <7091+mackato@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:37:43 +0900 Subject: [PATCH 8/8] =?UTF-8?q?yq=20=E6=9C=AA=E6=A4=9C=E5=87=BA=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=20devbox=20?= =?UTF-8?q?=E5=89=8D=E6=8F=90=E3=81=8B=E3=82=89=E4=B8=80=E8=88=AC=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mikefarah 版 yq(yq-go)が必要である旨と、wrapper 経由(nix run / devbox) なら同梱される旨・生スクリプト直使用時はインストールが要る旨を明示する。 Co-Authored-By: Claude Opus 4.8 --- pkgs/ghas-setup/ghas-setup | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/ghas-setup/ghas-setup b/pkgs/ghas-setup/ghas-setup index 8b9aaaf..78369fd 100755 --- a/pkgs/ghas-setup/ghas-setup +++ b/pkgs/ghas-setup/ghas-setup @@ -38,9 +38,9 @@ while [ $# -gt 0 ]; do esac done -# pre-flight: yq(mikefarah 版 = devbox の yq-go) +# pre-flight: yq(mikefarah 版 = yq-go) command -v yq >/dev/null 2>&1 || { - echo "yq が見つかりません。'devbox shell' 内で実行してください(devbox.json の yq-go)。" >&2 + echo "yq が見つかりません。mikefarah 版 yq(yq-go)が必要です。wrapper 経由(nix run .#ghas-setup や devbox)なら同梱されます。生スクリプトを直接使う場合は yq-go をインストールしてください。" >&2 exit 1 }