Files
2026-03-29 09:08:01 +08:00

153 lines
5.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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-15XX-YYXX>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 长度须在 1200 之间", 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()