日常更新
This commit is contained in:
152
ansible/tools/scaffold_doc_id.py
Normal file
152
ansible/tools/scaffold_doc_id.py
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
"""为执行域 doc_id 生成最小闭环骨架:docs + ansible/files + verify playbook。
|
||||
|
||||
符合 Epic 1 Story 1.3:默认通过 verify_common noop 任务链接入集群基线断言。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
DOCS_DIR = ROOT / "docs"
|
||||
FILES_BASE = ROOT / "ansible" / "files"
|
||||
VERIFY_DIR = ROOT / "ansible" / "playbooks" / "verify"
|
||||
|
||||
EXEC_ID_RE = re.compile(r"^(0[1-9]|[1-9][0-9])-(0[1-9]|[1-9][0-9])$")
|
||||
|
||||
|
||||
def doc_markdown(doc_id: str, slug: str, title: str) -> str:
|
||||
return f"""# {title}
|
||||
|
||||
> **doc_id**:`{doc_id}` · 执行域文档(参与 `verify.sh list/full`)。
|
||||
|
||||
## 契约与真源
|
||||
|
||||
- **清单真源目录**:`ansible/files/{doc_id}/`(不要在本文重复粘贴大块 YAML 作为第二真源)。
|
||||
- **Ansible 验证入口**:`ansible/playbooks/verify/{doc_id}.yml`
|
||||
- 基线断言复用 `ansible/roles/verify_common/tasks/noop-doc-verify.yml`(集群连通 + 可选 manifest dry-run)。
|
||||
|
||||
验收命令:
|
||||
|
||||
```bash
|
||||
./ansible/bin/verify.sh run {doc_id}
|
||||
```
|
||||
|
||||
## 正文
|
||||
|
||||
(编写步骤说明;引用清单时请写相对于仓库的路径,例如 `ansible/files/{doc_id}/demo.yaml`。)
|
||||
"""
|
||||
|
||||
|
||||
def files_readme(doc_id: str) -> str:
|
||||
return f"""# {doc_id} 清单真源
|
||||
|
||||
将本篇相关的 Kubernetes YAML、Helm values 等放在此目录。
|
||||
|
||||
- 命名建议:小写 + 连字符,例如 `app-deploy.yaml`。
|
||||
- 示例或非集群清单可使用 `*.example.yaml` 后缀;noop 验证会跳过这类文件的 `kubectl dry-run`。
|
||||
"""
|
||||
|
||||
|
||||
def verify_playbook(doc_id: str, doc_filename: str) -> str:
|
||||
return f"""---
|
||||
- name: "{doc_id} noop verify (scaffold)"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
vars:
|
||||
repo_root: "{{{{ playbook_dir }}}}/../../.."
|
||||
doc_id: "{doc_id}"
|
||||
doc_filename: "{doc_filename}"
|
||||
tasks:
|
||||
- name: Include noop doc verify role tasks
|
||||
ansible.builtin.include_role:
|
||||
name: verify_common
|
||||
tasks_from: noop-doc-verify.yml
|
||||
"""
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(
|
||||
description="生成执行域 doc_id 最小骨架:docs、ansible/files/<doc_id>/、verify playbook。"
|
||||
)
|
||||
p.add_argument("doc_id", help="如 04-15(XX-YY,XX>0 且 YY>0)")
|
||||
p.add_argument("slug", help="文档文件名段,如 k3s-traefik-acme(生成 docs/<doc_id>-<slug>.md)")
|
||||
p.add_argument("--title", default="", help="文档 H1 标题(默认从 slug 生成)")
|
||||
p.add_argument("--dry-run", action="store_true", help="只打印将创建的路径,不写盘")
|
||||
p.add_argument("--force", action="store_true", help="覆盖已存在的同名目标文件")
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
def validate_slug(slug: str) -> None:
|
||||
if not slug or len(slug) > 200:
|
||||
print("ERR: slug 长度须在 1~200 之间", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
if slug.strip() != slug:
|
||||
print("ERR: slug 首尾不应含空白", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
if "/" in slug or "\\" in slug or slug.startswith("."):
|
||||
print("ERR: slug 不允许路径分隔符或以 '.' 开头", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
doc_id = args.doc_id.strip()
|
||||
slug = args.slug.strip()
|
||||
if not EXEC_ID_RE.fullmatch(doc_id):
|
||||
print(f"ERR: doc_id 非法(须匹配执行域 XX-YY):{doc_id!r}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
validate_slug(slug)
|
||||
|
||||
doc_filename = f"{doc_id}-{slug}.md"
|
||||
title = args.title.strip() or slug.replace("-", " ").replace("_", " ")
|
||||
|
||||
target_doc = DOCS_DIR / doc_filename
|
||||
target_pb = VERIFY_DIR / f"{doc_id}.yml"
|
||||
files_dir = FILES_BASE / doc_id
|
||||
target_readme = files_dir / "README.md"
|
||||
|
||||
# 同一 doc_id 仅允许一篇 docs/<doc_id>-*.md(与 validate_matrix 一致)
|
||||
siblings = sorted(DOCS_DIR.glob(f"{doc_id}-*.md"))
|
||||
for p in siblings:
|
||||
if p.name != doc_filename:
|
||||
print(
|
||||
f"ERR: doc_id={doc_id} 已存在其他文档 {p.relative_to(ROOT)},"
|
||||
f"请先删除或合并,不能新增第二篇。",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
planned = [
|
||||
("docs", target_doc, doc_markdown(doc_id, slug, title)),
|
||||
("files README", target_readme, files_readme(doc_id)),
|
||||
("verify playbook", target_pb, verify_playbook(doc_id, doc_filename)),
|
||||
]
|
||||
|
||||
for label, path, content in planned:
|
||||
if path.exists() and not args.force:
|
||||
print(f"ERR: 已存在(使用 --force 覆盖):{path.relative_to(ROOT)}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
if args.dry_run:
|
||||
print("[dry-run] 将创建:")
|
||||
for label, path, _ in planned:
|
||||
print(f" - {label}: {path.relative_to(ROOT)}")
|
||||
return
|
||||
|
||||
for label, path, content in planned:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
print(f"[OK] wrote {path.relative_to(ROOT)}")
|
||||
|
||||
print("")
|
||||
print("下一步:编辑正文与清单,然后执行:")
|
||||
print(f" ./scripts/offline-check.sh")
|
||||
print(f" ./ansible/bin/verify.sh run {doc_id}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user