生成器可检测与管理远端主题素材(列表 / 差异 / 覆盖 / 删除)
「传上去」之后还得知道「传得对不对」:同一 slug 传过三次、换过 slug 旧图还在、 重新导出后远端仍是旧版,这些在页面上都看不出来。 - serve.py:s3_request 支持 query;新增 ListObjectsV2(含分页)与 DeleteObject, 端点 /api/s3/list、/api/s3/delete。 - index.html:远端素材清单按对象键名跟本地比 —— 一致 / 旧图(同名但大小不同)/ 多余(当前主题已不引用)/ 缺失。「补齐差异」只传缺的与旧图,「删除多余」批量删, 单行可 ↻ 覆盖或 ✕ 删除;删除走 confirm 二次确认。改过 slug 会提示重新检测。 - 修:list 返回完整键而 upload/delete 收相对键,不剥前缀会删到不存在的键 (S3 回 204,看起来删成功其实没删)。加 relKey() 统一。 - check_sigv4.py 补带 query 的用例(ListObjectsV2 三参数、乱序 token)→ 8/8; selftest_upload.py 补列表/分页/幂等删除 → 20 项全过。
This commit is contained in:
@@ -30,19 +30,25 @@ TS = "20130524T000000Z"
|
||||
EMPTY = serve.EMPTY_SHA256
|
||||
BODY_HASH = serve._sha256(b"TamaBox theme builder probe\n")
|
||||
|
||||
# (说明, method, endpoint, extra cfg, key)
|
||||
# (说明, method, endpoint, extra cfg, key, query)
|
||||
CASES = [
|
||||
("path-style 桶级 HEAD", "HEAD", "http://127.0.0.1:9000", dict(bucket="tb-assets"), ""),
|
||||
("path-style 桶级 HEAD", "HEAD", "http://127.0.0.1:9000", dict(bucket="tb-assets"), "", None),
|
||||
("path-style 带前缀对象", "PUT", "http://127.0.0.1:9000",
|
||||
dict(bucket="tb-assets", prefix="themes"), "shiroko/bg1.webp"),
|
||||
dict(bucket="tb-assets", prefix="themes"), "shiroko/bg1.webp", None),
|
||||
("virtual-host 对象", "GET", "https://s3.us-west-004.backblazeb2.com",
|
||||
dict(bucket="my-bucket", region="us-west-004", pathStyle=False), "themes/shiroko/bg1.webp"),
|
||||
dict(bucket="my-bucket", region="us-west-004", pathStyle=False), "themes/shiroko/bg1.webp", None),
|
||||
("R2 region=auto", "PUT", "https://acc.r2.cloudflarestorage.com",
|
||||
dict(bucket="cdn", region="auto", prefix="themes"), "x/a.webp"),
|
||||
dict(bucket="cdn", region="auto", prefix="themes"), "x/a.webp", None),
|
||||
("R2 虚拟主机", "GET", "https://acc.r2.cloudflarestorage.com",
|
||||
dict(bucket="cdn", region="auto", pathStyle=False), "themes/s/t.webp"),
|
||||
dict(bucket="cdn", region="auto", pathStyle=False), "themes/s/t.webp", None),
|
||||
("带端口 https", "DELETE", "https://minio.lan:9443",
|
||||
dict(bucket="static", region="us-east-1"), "themes/s/t.webp"),
|
||||
dict(bucket="static", region="us-east-1"), "themes/s/t.webp", None),
|
||||
# 带 query:ListObjectsV2 的 canonical query 要按键排序、逐项编码(/ 编成 %2F)
|
||||
("ListObjectsV2 三参数", "GET", "http://127.0.0.1:9000", dict(bucket="tb-assets"),
|
||||
"", {"list-type": "2", "max-keys": "1000", "prefix": "themes/shiroko/"}),
|
||||
("ListObjectsV2 分页(乱序传入)", "GET", "https://acc.r2.cloudflarestorage.com",
|
||||
dict(bucket="cdn", region="auto", pathStyle=False), "",
|
||||
{"prefix": "themes/", "continuation-token": "1/abc+d", "max-keys": "2", "list-type": "2"}),
|
||||
]
|
||||
|
||||
|
||||
@@ -68,20 +74,21 @@ def go_sign(payloads):
|
||||
|
||||
def main():
|
||||
payloads = []
|
||||
for _, method, endpoint, extra, key in CASES:
|
||||
for _, method, endpoint, extra, key, query in CASES:
|
||||
cfg, path = build(endpoint, extra, key)
|
||||
body = BODY_HASH if method == "PUT" else EMPTY
|
||||
payloads.append("\t".join([method, cfg.url(path), cfg.region, body, TS]))
|
||||
url = cfg.url(path) + (("?" + serve.canonical_query(query)) if query else "")
|
||||
payloads.append("\t".join([method, url, cfg.region, body, TS]))
|
||||
|
||||
go = go_sign(payloads)
|
||||
if len(go) != len(CASES):
|
||||
raise SystemExit("返回条数不对:go=%d cases=%d" % (len(go), len(CASES)))
|
||||
|
||||
bad = 0
|
||||
for (desc, method, endpoint, extra, key), g in zip(CASES, go):
|
||||
for (desc, method, endpoint, extra, key, query), g in zip(CASES, go):
|
||||
cfg, path = build(endpoint, extra, key)
|
||||
body = BODY_HASH if method == "PUT" else EMPTY
|
||||
py = serve.sign_v4(cfg, method, path, body, TS, cfg.host)
|
||||
py = serve.sign_v4(cfg, method, path, body, TS, cfg.host, serve.canonical_query(query))
|
||||
if g.rsplit("Signature=", 1)[-1] == py.rsplit("Signature=", 1)[-1]:
|
||||
print("OK %s" % desc)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user