Skip to content

fix_62589

fix_62589 #84

# WEKO3 リポジトリ(RCOSDP/weko)の .github/workflows/ に配置する。
#
# 【重要】このリポジトリは public。Actions のログ・artifact・PR コメントは誰でも読める。
# したがって:
# - 台帳(所見・実証結果を含む)とベースラインは **このリポジトリに置かない**。
# プライベートリポジトリ(RCOSDP/weko-secret)から Secret 経由で取得する。
# - 台帳は WEKO3 のブランチごとに内容が違う。既定ブランチを固定で見ると、ブランチ間の
# 経路差がそのまま差分として出続けて形骸化する。PR の head → base → 既定 の順で
# プライベートリポジトリの**同名ブランチ**を探して使う(Resolve inventory ref)。
# - 出力は --summary-only で **件数のみ**。URI や endpoint 名は出さない。
# 明細はプライベートリポジトリ側に置いたレポートで確認する。
#
# 必要な Secret:
# secrets.API_INVENTORY_REPO 取得元の private リポジトリ (RCOSDP/weko-secret)
# secrets.API_INVENTORY_SSH_KEY weko-secret に登録した read-only deploy key の秘密鍵
# deploy key を使うのは、対象が1リポジトリに構造的に限定され、読み取り専用で、
# 個人アカウントに紐づかないため(PAT より事故時の影響が小さい)。
# 未設定なら、このジョブは何もせずスキップする(fork からの PR でも安全)。
#
# 設置手順: tools/api-inventory/ci/README.md
name: API Inventory Drift
on:
pull_request:
branches: ['**']
workflow_dispatch:
jobs:
drift:
runs-on: ubuntu-latest
timeout-minutes: 60
# fork からの PR には Secret が渡らない。無駄に起動しない。
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # changed_rows.py が base..head の diff を取るため
- name: Check secrets
id: cfg
env:
REPO: ${{ secrets.API_INVENTORY_REPO }}
KEY: ${{ secrets.API_INVENTORY_SSH_KEY }}
run: |
if [ -n "$REPO" ] && [ -n "$KEY" ]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "::notice::API_INVENTORY_REPO / API_INVENTORY_SSH_KEY が未設定のためスキップします"
fi
# 使う台帳ブランチを決める。存在しない ref を checkout に渡すと落ちるので、
# 先に ls-remote で候補を絞る。
# 1. PR の head ブランチ名 台帳更新 PR が**未マージでも**拾える(順序制約を作らない)
# 2. PR の base ブランチ名 台帳を触らない PR、および両方マージ後の定常状態
# 3. 既定ブランチ 警告を出して続行(件数は当てにならない)
- name: Resolve inventory ref
id: ref
if: steps.cfg.outputs.enabled == 'true'
env:
REPO: ${{ secrets.API_INVENTORY_REPO }}
KEY: ${{ secrets.API_INVENTORY_SSH_KEY }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
BASE_REF: ${{ github.event.pull_request.base.ref || github.ref_name }}
run: |
set -eu
d=$(mktemp -d); trap 'rm -rf "$d"' EXIT
printf '%s\n' "$KEY" > "$d/key"; chmod 600 "$d/key"
export GIT_SSH_COMMAND="ssh -i $d/key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new"
# 出るのはブランチ名だけ。鍵も台帳の中身も出ない。
# 認証に失敗したらここで落とす(黙って既定ブランチに落ちるのを防ぐ)。
heads=$(git ls-remote --heads "git@github.com:$REPO.git")
have() { printf '%s\n' "$heads" | awk -v r="refs/heads/$1" '$2==r{f=1} END{exit !f}'; }
pick=''
if [ -n "${HEAD_REF:-}" ] && have "$HEAD_REF"; then
pick="$HEAD_REF"
elif [ -n "${BASE_REF:-}" ] && have "$BASE_REF"; then
pick="$BASE_REF"
fi
if [ -n "$pick" ]; then
echo "ref=$pick" >> "$GITHUB_OUTPUT"
echo "fallback=false" >> "$GITHUB_OUTPUT"
echo "::notice::台帳ブランチ: $pick"
else
echo "ref=" >> "$GITHUB_OUTPUT"
echo "fallback=true" >> "$GITHUB_OUTPUT"
echo "::warning::プライベートリポジトリに ${HEAD_REF:-} / ${BASE_REF:-} のいずれもありません。既定ブランチの台帳と比較します。ブランチ間の経路差がそのまま差分に出るため件数は当てになりません。同名ブランチを作ってください。"
fi
# 台帳・ベースラインをプライベートリポジトリから取得する。
# チェックアウト先は .api-inventory-data/(.gitignore 済み)。
# ref が空文字なら actions/checkout は既定ブランチを取る(上記フォールバック)。
- name: Checkout inventory data (private)
if: steps.cfg.outputs.enabled == 'true'
uses: actions/checkout@v4
with:
repository: ${{ secrets.API_INVENTORY_REPO }}
ref: ${{ steps.ref.outputs.ref }}
ssh-key: ${{ secrets.API_INVENTORY_SSH_KEY }}
path: .api-inventory-data
persist-credentials: false
- uses: actions/setup-python@v5
if: steps.cfg.outputs.enabled == 'true'
with:
python-version: '3.11'
- name: Start WEKO containers
if: steps.cfg.outputs.enabled == 'true'
run: |
chmod +x install.sh
./install.sh
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
- name: Wait for web container
if: steps.cfg.outputs.enabled == 'true'
run: |
for i in $(seq 1 60); do
if docker compose -f docker-compose2.yml exec -T web \
bash -lc 'source ~/.virtualenvs/invenio/bin/activate; invenio --help' >/dev/null 2>&1; then
echo "ready"; exit 0
fi
sleep 10
done
docker compose -f docker-compose2.yml logs web | tail -100
exit 1
- name: Run drift checks
if: steps.cfg.outputs.enabled == 'true'
env:
WEKO_API_INVENTORY_DIR: ${{ github.workspace }}/.api-inventory-data
run: |
set -o pipefail
T=tools/api-inventory/scripts
# 実機 url_map からスナップショットを作る(生成物は公開領域に置かない)
python3 $T/snapshot.py --out /tmp/api_snapshot.new.json --profile default
# 以降はすべて --summary-only。件数だけを標準出力に出す。
python3 $T/diff_snapshot.py \
"$WEKO_API_INVENTORY_DIR/api_snapshot.json" /tmp/api_snapshot.new.json \
--summary-only --gate --out /tmp/drift.md
python3 $T/reconcile.py \
--snapshot /tmp/api_snapshot.new.json \
--summary-only --gate --out /tmp/reconcile.md
- name: Probe changed endpoints
if: always() && steps.cfg.outputs.enabled == 'true'
env:
WEKO_API_INVENTORY_DIR: ${{ github.workspace }}/.api-inventory-data
run: |
T=tools/api-inventory/scripts
# 変更が触れた台帳行を割り出す(no のみを出力。URIは出さない)
python3 $T/changed_rows.py \
"${{ github.event.pull_request.base.sha || github.event.before }}" "${{ github.sha }}" \
--out /tmp/rerun_nos.txt > /dev/null
# install.sh はレコードを作らないので最小コーパスを投入してから測る
python3 $T/fixtures.py --out /tmp/fixtures.json
python3 $T/probe_ci.py \
--fixtures /tmp/fixtures.json --only /tmp/rerun_nos.txt \
--allow-writes --summary-only --gate --out /tmp/probe.json
# artifact は件数のみのサマリに限定する(public なので誰でも取得できる)。
# drift.md / reconcile.md は --summary-only で生成済み。
# probe.json / api_snapshot.new.json は明細を含むため **上げない**。
- name: Upload summary (counts only)
if: always() && steps.cfg.outputs.enabled == 'true'
uses: actions/upload-artifact@v4
with:
name: api-inventory-summary
path: |
/tmp/drift.md
/tmp/reconcile.md
- name: Comment on PR (counts only)
if: always() && steps.cfg.outputs.enabled == 'true' && github.event_name == 'pull_request'
uses: actions/github-script@v7
env:
INVENTORY_REF: ${{ steps.ref.outputs.ref }}
INVENTORY_FALLBACK: ${{ steps.ref.outputs.fallback }}
with:
script: |
const fs = require('fs');
const read = (p, title) => {
try { return `\n\n### ${title}\n\n` + fs.readFileSync(p, 'utf8'); }
catch (e) { return `\n\n### ${title}\n\n(生成されませんでした)`; }
};
// どの台帳と比べた件数なのかが分からないと、レビュアは数字を判断できない。
const ref = process.env.INVENTORY_REF;
const fallback = process.env.INVENTORY_FALLBACK;
let body = '## API インベントリ差分(件数のみ)\n\n';
if (fallback === 'true') {
body += '> ⚠️ **プライベートリポジトリに対応ブランチが無いため、既定ブランチの台帳と比較しています。**\n'
+ '> ブランチ間の経路差がそのまま差分として出るので、以下の件数は当てになりません。\n'
+ '> この PR のブランチと同名のブランチをプライベートリポジトリに作ってください。\n\n';
} else if (ref) {
body += `> 台帳ブランチ: \`${ref}\`\n\n`;
} else {
body += '> ⚠️ 台帳ブランチを解決できませんでした(Resolve inventory ref が失敗しています)。\n\n';
}
body += '> 明細は公開できないため件数のみ表示しています。'
+ '該当箇所はプライベートリポジトリ側の台帳・レポートで確認してください。';
body += read('/tmp/drift.md', 'ベースラインとの差分');
body += read('/tmp/reconcile.md', '台帳との突き合わせ');
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body.slice(0, 60000),
});
- name: Teardown
if: always() && steps.cfg.outputs.enabled == 'true'
run: docker compose -f docker-compose2.yml down -v