Skip to content

fix(device): self-diagnosing errors for malformed ObjectId args (IM-3248) - #27

Merged
j3r0lin merged 5 commits into
mainfrom
fix-IM-3248-objectid-validation
Aug 28, 2026
Merged

fix(device): self-diagnosing errors for malformed ObjectId args (IM-3248)#27
j3r0lin merged 5 commits into
mainfrom
fix-IM-3248-objectid-validation

Conversation

@j3r0lin

@j3r0lin j3r0lin commented Aug 28, 2026

Copy link
Copy Markdown
Member

问题现象

用非 ObjectId 的值当设备 ID(或 client/asset/group id 等)查询时,incloud-cli 只输出:

Error: HTTP 404: 404 page not found

这条错误没有任何上下文,人和 AI 工具都无法判断到底是「资源不存在」还是「参数本身就不对」。

为什么定位在 CLI,而不是网关或后端

实测对比过三种请求:

  • GET /api/v1/devices/17572(非法 ID)→ 404,content-type: text/plain,body 是 404 page not found,没有 x-trace-id/x-upstream-addr。这是网关(Traefik fork)默认路由的 http.NotFoundHandler() 吐出来的:网关按 ObjectId 形状匹配路由,非法 ID 拼出的路径匹配不到任何路由规则,根本没进到后端服务。
  • GET /api/v1/devices/507f1f77bcf86cd799439011(合法但不存在)→ 404,content-type: application/json,body 是 {"error":"resource_not_found","status":404,"message":"..."}。这条已经是正确的资源级 404。

两种 404 长得像但含义完全不同,且非法 ID 的请求根本没有触达 nezha-device-manager。所以网关和后端都不需要改,问题在 CLI:它应该在本地就能判断「17572」不是合法 ID,不必等一次网络往返才收到一条没有信息量的错误。

改了什么

  1. internal/cmdutil/objectid.go(新增):本地校验 24 位十六进制 ObjectId 的 cobra Args 校验器族(单个位置参数 / CSV 列表 / 变长参数全部校验),错误信息包含原始值、期望格式样例、以及可直接复制执行的查找命令(如 incloud device list -q 17572)。资源名和查找命令都是参数化的,不同资源(device / client / asset / device group / 及有父子关系的子资源如 project、layerfs、config snapshot)各自传入合适的提示命令。

  2. 挂载校验器:给 internal/cmd/device/ 下所有把位置参数直接拼进 URL 当 ID 用的命令挂上对应校验器(device id / client id / asset id / device group id,含 CSV 及变长参数场景,含按父资源 ID 二次校验子资源 ID 的场景,如 device group project get <group-id> <project-id>)。

    跳过的位置参数(逐个核实过,均因缺乏可复制执行的查找命令,或参数本身不是 ID):

    • device exec cancel(diagnosis id)—— 没有对应的 list 命令可用于查找
    • device config task get / device import-status(job id)—— 同上
    • device uplink get / device uplink perf(uplink id)—— 命令本身是裸单参数,没有父级 ID 可用来构造有意义的查找命令
    • device config schema get(json key,如 dns)—— 不是 ID
    • device exec cli 的第二个参数(要执行的 shell 命令文本)—— 不是 ID
    • device import <file>(本地文件路径)—— 不是 ID

    这些命令仍然会命中下面的网关兜底。

  3. internal/api/rest.go 兜底execute()(及 Download())在响应为 404 且 body 去掉首尾空白后正好是 404 page not found 时,把错误文本换成:

    HTTP 404: no API route matched "<实际请求路径>" — the path or one of its id segments is malformed (ids are 24-character hex ObjectIds); this is not a "resource not found" response
    

    这条覆盖所有没挂本地校验器的命令,以及 ID 拼接路径本身写错的情况。*HTTPError 的类型和 StatusCode 语义保持不变,errors.As 调用方(如 edge_cli_config.go)不受影响。

  4. 补充单元测试:internal/cmdutil/objectid_test.go(校验器行为)、internal/api/rest_integration_test.go(网关 404 兜底),并把现有测试里用作 ID 的占位符(c1device123abc123 等)替换成合法形状的 ObjectId。

验证

make fmt && make lint && make build && make test 全部通过(make lint 报的 2 条 nolintlintimportcmd.go/log_diagnostic.go 里已存在于 main 分支的问题,与本次改动无关,已核实)。

在 dev 环境用构建出的二进制实测三种场景:

1. 非法 ID

$ ./bin/incloud device get 17572
invalid device id "17572": expected a 24-character hex ObjectId (e.g. 507f1f77bcf86cd799439011); run 'incloud device list -q 17572' to find the device id

2. 合法但不存在的 ID

$ ./bin/incloud device get 507f1f77bcf86cd799439011
HTTP 404: {"error":"resource_not_found","status":404,"message":"An error occurred while processing your request"}

3. 正常 ID

$ ./bin/incloud device list --limit 1 -o json
{"limit":1,"page":1,"result":[{"name":"IM-3043-verify",...,"_id":"6a69bd5b7b5c384e6545b1c4"}],...}

$ ./bin/incloud device get 6a69bd5b7b5c384e6545b1c4
{"name":"IM-3043-verify","serialNumber":"MR8051234502461",...,"_id":"6a69bd5b7b5c384e6545b1c4"}

关联 IM-3248。

IM-3248: local validation of positional args used as ObjectIds in API
paths, with self-diagnosing errors (offending value, expected format,
copy-pasteable lookup command).
IM-3248: the gateway (Traefik fork) returns a generic
'404 page not found' text/plain body when a request path doesn't match
any route shape (e.g. a malformed ObjectId segment) — indistinguishable
from a real resource-not-found response, which is JSON. Detect that
exact body and rewrite it into a message that names the request path
and explains it's a routing mismatch, not a missing resource. Acts as
a fallback for commands that don't yet validate their id arguments
locally.
IM-3248: needed by subordinate-resource commands (e.g. group layerfs/
project bulk delete) that validate a CSV id list scoped by an
already-validated parent id rather than the offending value itself.
IM-3248: incloud-cli only surfaced the gateway's generic
'404 page not found' when a non-ObjectId value (e.g. a serial number
or typo) was passed where a device/client/asset/group id was expected
— indistinguishable from a real resource-not-found response. Wire the
new cmdutil ObjectId validators onto every device-package command that
uses a positional argument directly as an id in its API path, so the
mistake is caught locally with a message naming the offending value,
the expected format, and a copy-pasteable lookup command.

Left unvalidated (no reliable copy-pasteable lookup command exists,
or the argument isn't an id): device exec cancel (diagnosis id),
device config task get / device import-status (job id), device uplink
get/perf (bare uplink id, no parent to scope a lookup by), device
config schema get (json key, not an id), device exec cli's command
argument, and device import's file path. These fall back to the
gateway 404 message from the api package fix.
IM-3248: existing tests used placeholder ids like 'c1', 'device123',
'abc123' for positional args that are now validated as ObjectIds.
@j3r0lin
j3r0lin merged commit 0da957a into main Aug 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant