Claude Media
資産台帳のサポート終了日を突合する — endoflife.date APIとClaude Codeで一覧化

資産台帳のサポート終了日を突合する — endoflife.date APIとClaude Codeで一覧化

PC・サーバーの資産台帳をendoflife.dateのAPIと突き合わせ、サポート終了までの残日数で並べる手順です。製品名の名寄せ、Windowsのエディション別サイクルなど、取り違えを見つける確認も扱います。

PC・サーバーの資産台帳には、OSやミドルウェアの名前とバージョンが並んでいます。ここからサポートが切れる順に並べ替えた一覧を作るには、製品ごとのサポート終了日(EOL)を別に引いてくる必要があります。

endoflife.dateは、製品ごとのリリース系統とサポート期限をAPIで返します。台帳の記載をこのAPIの製品IDと系統名に寄せれば、残り日数で並べた一覧を自動で作れます。難しいのはAPIを呼ぶ部分ではありません。「Ubuntu Server 22.04.5 LTS」のような台帳の表記を、どの系統に当てるかという名寄せです。この記事は、突合スクリプトの実物と、名寄せの誤りを見つける確認までを順に書きます。

突合で使うendoflife.date APIの取り出し方

APIのベースURLは https://endoflife.date/api/v1 で、認証キーは不要です。突合に使うのは、次の2つのエンドポイントです。

エンドポイント返るもの
/products返るもの製品の一覧(name・別名・カテゴリ)
/products/{product}返るもの1製品の全リリース系統と、各系統の期限

製品一覧は484件が返ります。台帳の製品数が数十でも、系統ごとに呼ぶ必要はありません。/products/{product} が全系統をまとめて返すので、製品ごとに1回呼べば足ります。

返却JSONの result.releases に系統ごとのオブジェクトが並び、突合で読む項目は次のとおりです。

項目意味
name意味系統名(22.04、2016、11-23h2-w など)
label意味表示名(22.04 'Jammy Jellyfish' (LTS) など)
eolFrom意味系統の終了日。日付が不明なときは null
isEol意味系統がEOLかどうか
isMaintained意味延長サポートを含め、何らかのサポートが残っているか
eoesFrom意味延長サポートの終了日。延長サポートの無い製品には項目自体が無い

eolFrom が null の系統が存在する点には注意が必要です。APIの仕様でも、日付が不明なときは null になると説明されています。実際にnginxの最新系統(1.31、1.30)は eolFrom が null で、isEol は false でした。残り日数を計算するスクリプトは、日付なしの行を「未定」として扱い、並べ替えの末尾に置く必要があります。

もう1つ、isEol と isMaintained は別物です。たとえばWindows Server 2016は、メインストリームに当たる eoasFrom が2022-01-11で過ぎており、eolFrom は2027-01-12、eoesFrom は2030-01-12と3つの日付が並びます。一覧に載せる日付を eolFrom に決めておかないと、担当者ごとに別の列を見てしまいます。延長サポートを契約済みの機器があるなら、eoesFrom を別列で足します。

台帳を製品IDと系統に寄せる対応表を作る

名寄せは、台帳の表記ゆれを製品IDに寄せる作業です。台帳側の「Microsoft SQL Server」と、API側の mssqlserver は、文字列が似ていません。この対応は人が決めて、ファイルに残します。台帳CSVとは別に、対応表を1枚持ちます。

product_raw,product_id,cycle_pattern
Ubuntu Server,ubuntu,^{major}\.{minor}$
Microsoft SQL Server,mssqlserver,label:2016 SP3$
PostgreSQL,postgresql,^{major}$
Windows Server,windows-server,^{year}$
CentOS,centos,^{major}$
nginx,nginx,^{major}\.{minor}$
Windows 11,windows,label:^11 23H2 \(W\)$

cycle_pattern は、台帳のバージョン欄から数字を取り出して系統名に当てる正規表現です。{major} {minor} {year} は台帳の数字に置き換わります。label: を頭に付けた行は、系統名ではなく表示名(label)に当てます。系統名がバージョン番号でも年でもない製品のためです。

この表が必要になる理由は、系統名の付き方が製品ごとにばらばらだからです。実際のAPIでは、次のような違いがありました。

台帳の表記製品ID系統名の実例
Ubuntu 22.04.5 LTS製品IDubuntu系統名の実例22.04
PostgreSQL 15.8製品IDpostgresql系統名の実例15
Windows Server 2016製品IDwindows-server系統名の実例2016
SQL Server 2016 SP3製品IDmssqlserver系統名の実例13.0-sp3 と 13.0-sp3-acp
Windows 11 23H2 Pro製品IDwindows系統名の実例11-23h2-w

台帳の「22.04.5」はパッチ番号まで付いていますが、APIの系統は 22.04 までです。PostgreSQLは15.8のメジャー番号だけを見て 15 に当てます。SQL Serverは内部バージョンの 13.0 が系統名で、台帳の「2016」とは表示名(label)でしか結べません。

突合スクリプトの実物と実行結果

対応表と台帳CSVを読み、製品ごとに1回だけAPIを呼んで、残り日数で並べるスクリプトです。標準ライブラリだけで動きます。

import csv, json, re, urllib.request
from datetime import date
 
API = "https://endoflife.date/api/v1/products/"
cache = {}
 
def fetch(pid):
    if pid not in cache:
        req = urllib.request.Request(API + pid, headers={"User-Agent": "asset-eol-check/0.1"})
        with urllib.request.urlopen(req, timeout=30) as r:  # 301 は自動で追従される
            cache[pid] = json.load(r)["result"]["releases"]
    return cache[pid]
 
def pick(releases, pattern, ver):
    nums = re.findall(r"\d+", ver)
    sub = {"major": nums[0] if nums else "",
           "minor": nums[1] if len(nums) > 1 else "",
           "year": nums[0] if nums else ""}
    if pattern.startswith("label:"):
        return [r for r in releases if re.search(pattern[6:], r["label"])]
    return [r for r in releases if re.search(pattern.format(**sub), r["name"])]
 
mapping = {r["product_raw"]: r for r in csv.DictReader(open("product_map.csv", encoding="utf-8"))}
today = date.today()
rows, problems = [], []
for a in csv.DictReader(open("assets.csv", encoding="utf-8")):
    m = mapping.get(a["product_raw"])
    if not m:
        problems.append((a["host"], "対応表に無い製品名", a["product_raw"])); continue
    try:
        hits = pick(fetch(m["product_id"]), m["cycle_pattern"], a["version_raw"])
    except Exception as e:
        problems.append((a["host"], f"取得失敗 {e}", m["product_id"])); continue
    if len(hits) != 1:
        problems.append((a["host"], f"系統が{len(hits)}件に一致", a["version_raw"])); continue
    c = hits[0]
    eol = c["eolFrom"]
    left = (date.fromisoformat(eol) - today).days if eol else None
    rows.append((left if left is not None else 10**9, a["host"], m["product_id"],
                 c["name"], c["label"], eol or "未定", c["isEol"]))
 
for left, host, pid, cyc, label, eol, is_eol in sorted(rows):
    d = "-" if left == 10**9 else f"{left:>6}"
    print(f"{host:6} {pid:15} {cyc:8} {label:34} {eol:11} 残り{d}日  EOL済み={is_eol}")
print("--- 突合できなかった行 ---")
for p in problems:
    print(*p)

次の9行の台帳(assets.csv)で動かしました。最後の1行は、対応表に載せていない製品名をわざと入れたものです。

host,role,product_raw,version_raw
web01,Webサーバー,Ubuntu Server,22.04.5 LTS
web02,Webサーバー,Ubuntu Server,20.04
db01,DB,Microsoft SQL Server,2016 SP3
db02,DB,PostgreSQL,15.8
app01,アプリ,Windows Server,2016 Standard
app02,アプリ,CentOS,7.9
lb01,LB,nginx,1.24.0
pc001,PC,Windows 11,23H2 Pro
x01,検証,Oracle Foo,1.0
python3 eol_check.py

出力は次のとおりです。残り日数は実行した日で変わります。ここでの実行日は2026-10-13です。

lb01   nginx           1.24     1.24                               2024-04-23  残り  -903日  EOL済み=True
app02  centos          7        7                                  2024-06-30  残り  -835日  EOL済み=True
web02  ubuntu          20.04    20.04 'Focal Fossa' (LTS)          2025-05-31  残り  -500日  EOL済み=True
pc001  windows         11-23h2-w 11 23H2 (W)                        2025-11-11  残り  -336日  EOL済み=True
db01   mssqlserver     13.0-sp3 2016 SP3                           2026-07-14  残り   -91日  EOL済み=True
app01  windows-server  2016     Windows Server 2016 (LTSC)         2027-01-12  残り    91日  EOL済み=False
web01  ubuntu          22.04    22.04 'Jammy Jellyfish' (LTS)      2027-06-01  残り   231日  EOL済み=False
db02   postgresql      15       15                                 2027-11-11  残り   394日  EOL済み=False
--- 突合できなかった行 ---
x01 対応表に無い製品名 Oracle Foo

負の値は、すでに終了日を過ぎている機器です。上から順に読めば、対応の優先順位がそのまま並びます。この例ではWindows Server 2016が残り91日で、期限が近い側の先頭にいます。

名寄せの誤りを見つける確認

一覧が出ても、名寄せが合っている保証にはなりません。突合の失敗は、エラーにならず「別の系統の日付が正しい顔で出る」形で起きます。スクリプトに仕込んである確認と、出力で目視する点を挙げます。

一致件数が1件でない行は一覧に入れない

pick が返す系統が0件なら対応表の誤り、2件以上なら絞り込みの不足です。スクリプトは1件に定まらない行を一覧に入れず、末尾の「突合できなかった行」に回します。SQL Server 2016 SP3は、APIに 13.0-sp3 と 13.0-sp3-acp(Azure Connect Pack)の2系統があり、表示名の末尾を $ で止めてやっと1件になりました。止めないと2件に一致します。

表示名を並べて、台帳の表記と目で突き合わせる

出力の4列目は、APIが返す label です。「Windows Server 2016 (LTSC)」と台帳の「Windows Server 2016 Standard」が同じ製品を指すかは、人が読めば数秒で分かります。名寄せが誤ると、ここに別の製品の名前が出ます。

Windowsはエディションで終了日が変わる

Windows 11は、同じ23H2でも系統が2つあります。11-23h2-w と 11-23h2-e です。endoflife.dateのWindowsのページは、(W) をHome・Pro・Pro Education・Pro for Workstations、(E) をEnterprise・Education・IoT Enterpriseと説明しています。今回の台帳の「Windows 11 23H2 Pro」は (W) に当たるので、対応表も (W) で止めています。台帳にエディション欄が無いと、ここは決められません。

別名は301で転送される

製品IDには別名があります。たとえば mssql は mssqlserver に、node は nodejs に、いずれも301で転送されました。APIの仕様も、製品名が改名され得るので301に従うよう求めています。urllib は転送を自動で追うので動きますが、curl で確認するときは -L を付けます。対応表には、転送先の正式な製品IDを書いておくのが確実です。

404の本文はJSONではない

存在しない製品IDを呼ぶと、ステータスは404で、本文はJSONではなくHTMLのエラーページです。仕様にも、この点が注記されています。json.load が例外を投げるので、スクリプトは取得失敗として問題の一覧に回します。

Claude Codeに任せる範囲と、人が確認する範囲

ここまでの手順のうち、Claude Codeに任せやすいのは「台帳の表記を洗い出して対応表の候補を作る」ことと「スクリプトを回して未一致の行を読む」ことです。逆に、対応表の最終確定は人が持ちます。

対応表の叩き台を作らせる

台帳CSVから product_raw の重複を除いた一覧を作らせ、製品一覧のAPIの結果と照らして、product_id の候補を埋めさせます。ここで出てくる product_id は候補です。Claudeの記憶から埋めた製品IDは、存在しない名前かもしれません。次のコマンドで、候補が実在する製品IDかをAPIに確かめさせます。

curl -sL https://endoflife.date/api/v1/products \
  | python3 -c "import json,sys; print('\n'.join(p['name'] for p in json.load(sys.stdin)['result']))" \
  | grep -x -f <(cut -d, -f2 product_map.csv | tail -n +2)

出力に出なかった製品IDは、APIに存在しない名前です。対応表の行を直す対象になります。

CLAUDE.mdに突合のルールを書く

名寄せの規約をプロジェクトのCLAUDE.mdに置くと、毎回の依頼で言い直さずに済みます。たとえば次のような形です。

## EOL突合のルール
- 製品IDは product_map.csv に書いたものだけを使う。記憶から補わない
- 系統が1件に定まらない行は一覧に入れず、未一致として報告する
- 終了日の列は eolFrom を使う。eolFrom が null の系統は「未定」と書く
- Windowsはエディション(W/E)が台帳に無ければ、推測せず未一致にする

実行権限を絞って定期実行する

月次などで回すなら、claude -p で非対話実行にできます。公式のheadlessのページには、--allowedTools で自動承認するツールを絞る書き方が載っています。出力は --output-format json で構造化できます。スクリプトの実行だけを許可するなら、.claude/settings.json に次のように書けます。許可規則は、Bash(npm run build) のように、コマンドの文字列をそのまま書いた形が完全一致の規則になります。

{
  "permissions": {
    "allow": ["Bash(python3 eol_check.py)"],
    "deny": ["Read(./.env)"]
  }
}

claude -p の基本はClaude Codeの-pモード解説にあります。同じく外部のAPIを呼んで、IDの取り違えを防ぐ確認を組み込む例は、e-Stat APIをClaude Codeで呼ぶ手順にまとめています。

未一致の一覧を読ませて、対応表を直す反復

初回は未一致の行が必ず出ます。出力の「突合できなかった行」をClaudeに読ませ、対応表の行を足すか直すかの案を出させます。人が案を確かめて反映し、もう一度スクリプトを回します。未一致が0件になり、かつ4列目の表示名が台帳の表記と合っていれば、名寄せは完了です。

一覧を台帳に戻すときの注意

APIが返す日付は、endoflife.dateに記載された終了日です。調達や契約の根拠にする日付は、ベンダーの公式ページで確かめます。製品のレスポンスには links.releasePolicy があり、たとえばWindowsならMicrosoftのライフサイクルのページ、UbuntuならUbuntuのリリース一覧のページを指します。仕様では、公開されたリリースポリシーが無い製品はこの項目が null になります。

呼び出しの回数にも気を配ります。仕様には、短時間に呼び過ぎると 429 が返り、Retry-After ヘッダで待ち時間が示されることがあると書かれています。このスクリプトは製品ごとに1回しか呼ばないので、台帳が数百台でも製品が数十種なら呼び出しは数十回です。台帳の行数ではなく、製品の種類数で呼び出し回数が決まります。

また、台帳側の更新も一覧の精度を決めます。台帳のバージョン欄が導入時のままだと、突合は古いバージョンの終了日を返します。バージョン欄が最新かは、機器上のコマンドで確かめます。製品のレスポンスには versionCommand があり、Ubuntuなら cat /etc/os-release と返ります。仕様では、コマンドが分からない製品はこの項目が null です。

仕様の変更への備えとして、読み取るのは name label eolFrom isEol の4項目に絞っておきます。仕様は、新しい項目が追加されても動くクライアントを前提にしています。

まとめ

突合の本体は、APIを呼ぶことより、台帳の表記を製品IDと系統に寄せる対応表にあります。対応表は人が確定して残し、スクリプトは1件に定まらない行を一覧に入れず、表示名を並べて目で確かめられる形にしておく。これで、別の系統の日付が正しい顔で出る失敗を避けられます。Claude Codeには、叩き台の作成と未一致行の読み取りを任せ、最終確認は人が持つ分担が安定します。

この記事を共有:XはてブLinkedIn