Skip to content

fix(servers): 改用 location 取代已被 Hetzner 移除的 datacenter 欄位 - #53

Merged
terry90918 merged 2 commits into
mainfrom
fix/hetzner-datacenter-removal
Jul 8, 2026
Merged

fix(servers): 改用 location 取代已被 Hetzner 移除的 datacenter 欄位#53
terry90918 merged 2 commits into
mainfrom
fix/hetzner-datacenter-removal

Conversation

@terry90918

Copy link
Copy Markdown
Collaborator

取代 #52(該 PR 的 head develop 落後 main 16 個 commit,含多項 security hardening,直接合併會回退)。本分支直接基於 origin/main,零衝突。

問題

Hetzner Cloud API 於 2026-06-30 正式從 Servers 與 Primary IPs 資源移除 datacenter 屬性(2025-12-16 公告:Phasing out Datacenters in favor of Locations)。

HetznerServerSchemadatacenter 列為 required,formatServer 讀取 server.datacenter.location.*,導致所有 server 相關工具一律拋出:

Invalid input: expected object, received undefined (servers.0.datacenter)

影響 hetzner_list_servers / hetzner_get_server / metrics / ssh 等工具——目前線上就是壞的。

修法

改用 API 早已提供的頂層 location 物件,且只宣告 formatServer 實際渲染的三個欄位name / city / country)。

zod 的 z.object 預設就會剝除未宣告的多餘欄位,因此宣告得越少對上游變更越寬容;反之,一個「宣告為必要卻從未讀取」的欄位就是定時炸彈——這次的 datacenter 正是如此。

為什麼不用 .passthrough()

實測 zod 4.3.6:

多出新欄位 缺少必要欄位
預設 z.object PASS(自動剝除) THROW
.passthrough() PASS(保留) THROW

passthrough 對這次的失效模式毫無防護作用,加了只會誤導。

驗證

  • 351 tests / typecheck / lint 全綠(含 main 上的 security 測試)
  • grep -rn '\.datacenter' src/ → 無殘留
  • 新增測試以真實 API 形狀(無 datacenter、含 latitude/longitude/network_zone 等未宣告欄位)驗證 schema 可正常解析
  • runtime 實打 Hetzner APIhetzner_list_servers 回傳 isError: false**Location**: Nuremberg, DE (nbg1)

Test plan

  • bun run test(351 passed)
  • bun run typecheck
  • bun run lint
  • build 後對真實 Hetzner API 呼叫 hetzner_list_servers / hetzner_get_server
  • merge 後 release-please 產生 release PR → npm publish

⚠️ 附帶發現(不在本 PR 範圍)

develop 落後 main 16 個 commit,且兩者在 src/tools/server-ssh.tssrc/tools/storage-boxes.tssrc/utils.ts 有 18 個衝突區塊(SSH host-key pinning、path-traversal 防護 vs storage-boxes 功能)。需要另外處理,否則下一個走 develop 的 PR 都會撞到同樣問題。

Hetzner Cloud API 於 2026-06-30 正式從 Servers 與 Primary IPs 資源移除
`datacenter` 屬性(2025-12-16 公告 "Phasing out Datacenters in favor of
Locations",https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters)。

原本 HetznerServerSchema 將 `datacenter` 列為 required,且 formatServer 讀取
`server.datacenter.location.*`,導致移除後所有 hetzner_list_servers /
hetzner_get_server / metrics / ssh 相關工具一律拋出:

  Invalid input: expected object, received undefined (servers.0.datacenter)

改用 API 早已提供的頂層 `location` 物件,且**只宣告 formatServer 實際會渲染的
三個欄位**(name / city / country)。zod 的 z.object 預設就會剝除未宣告的多餘
欄位,因此宣告得越少對上游變更越寬容;反之,一個「宣告為必要卻從未讀取」的欄位
就是一顆定時炸彈——這次的 datacenter 正是如此。

why 不用 .passthrough():實測 zod 4.3.6,預設 z.object 對「多出來的欄位」本來
就 PASS(自動剝除),passthrough 只是改為保留;而兩者對「缺少必要欄位」一律
THROW。也就是說 passthrough 對這次的失效模式毫無防護作用,加了只會誤導。

驗證:
- 346 unit tests / typecheck / lint 全綠
- 新增測試以真實 API 形狀(無 datacenter、含 latitude/longitude/network_zone
  等未宣告欄位)驗證 schema 可正常解析
- runtime 實打 Hetzner API:hetzner_list_servers 與 hetzner_get_server 均
  回傳 "**Location**: Nuremberg, DE (nbg1)"
Copilot AI review requested due to automatic review settings July 8, 2026 09:40
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e6cfeab6-11dd-4816-9f88-8a8bd3f90bac

📥 Commits

Reviewing files that changed from the base of the PR and between b83e7fe and 7dfcb0e.

📒 Files selected for processing (5)
  • src/tools/servers.ts
  • src/types.ts
  • tests/tools/metrics.test.ts
  • tests/tools/server-ssh.test.ts
  • tests/tools/servers.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hetzner-datacenter-removal

Comment @coderabbitai help to get the list of available commands.

@terry90918 terry90918 added the bug Something isn't working label Jul 8, 2026
@terry90918 terry90918 self-assigned this Jul 8, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

此 PR 針對 Hetzner Cloud API 於 2026-06-30 移除 datacenter 欄位所造成的 server 相關工具全面解析失敗問題,改以頂層 location 欄位為資料來源,恢復 hetzner_list_servers / hetzner_get_server 等工具可正常運作。

Changes:

  • 更新 HetznerServerSchema:移除 datacenter,改宣告並使用頂層 location(僅保留 formatter 會用到的欄位)。
  • 更新 servers formatter:formatServer() 改用 server.location.* 產生 Location 顯示。
  • 調整並新增測試:更新 server mock payload,新增 location rendering 與「未知欄位可被容忍」的 regression 測試。

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/types.ts 將 Server schema 從 datacenter 遷移到頂層 location,避免 Zod 因 required 欄位消失而拋錯
src/tools/servers.ts formatServer() 改以 server.location 渲染 Location 行
tests/tools/servers.test.ts 更新 mock server shape 並新增 location regression 測試(含 unknown keys 容忍測試)
tests/tools/server-ssh.test.ts 更新 mock server payload:以 location 取代 datacenter
tests/tools/metrics.test.ts 更新 mock server payload:以 location 取代 datacenter

Comment thread tests/tools/servers.test.ts Outdated
Copilot review 指出 baseServer 標註為 HetznerServer,但 location 仍帶著
id / description——schema 已不再宣告這兩個欄位,測試資料因此與 parse 後的實際
形狀不一致。

追查發現這個不一致之所以沒被 typecheck 抓到,是因為 tsconfig.json 的 include
只有 `src/**/*`,測試檔從來就不在 typecheck 範圍內。

fixture 收斂為 { name, country, city }。真實 API 多回傳的欄位
(id / description / latitude / longitude / network_zone)仍由既有的
rawApiServer 測試涵蓋,該測試刻意使用未標註型別的字面值。

註:把 tests/ 納入 typecheck 需另建 tsconfig(現有 rootDir 指向 src,且
tests/api.test.ts、tests/tools/volumes.test.ts 等有 4 個既有型別錯誤),
不在本 hotfix 範圍。

驗證:351 tests / typecheck / lint 全綠。
@terry90918
terry90918 merged commit b8d97ce into main Jul 8, 2026
4 checks passed
@terry90918
terry90918 deleted the fix/hetzner-datacenter-removal branch July 8, 2026 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants