From 60b914641ab61c7596d68be30dac825c2de6afc2 Mon Sep 17 00:00:00 2001 From: hellices Date: Sat, 15 Aug 2026 23:25:59 +0900 Subject: [PATCH 1/2] docs: index the runnable labs in the repository README A lab that deploys real Azure resources is hard to find from the root README. List the labs that ship their own azure.yaml, with the cost and time a reader needs before starting, and keep the list honest with tests that fail when a lab is missing, duplicated, or linked to a path that does not exist. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 12 ++ .../scripts/tests/test_repo_readme.py | 116 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py diff --git a/README.md b/README.md index 43a225c1..5fc354c1 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,18 @@ - 코드 예제와 함께 상세한 설명 제공 - 한국 고객 환경에 특화된 가이드 작성 +## 🧪 실습 랩 + +문서만 읽는 가이드와 달리, 실제 Azure 리소스를 배포해 직접 돌려 보는 실습입니다. 각 랩은 자체 `azure.yaml`을 가지고 있어 `azd up` 한 번으로 환경이 만들어집니다. + +| 랩 | 무엇을 확인하나 | 소요 시간 | 과금 | +|---|---|---|---| +| [Azure SRE Agent 이벤트 기반 장애 분석](monitor/sre-agent-event-lab/README.md) | 장애를 세 번 주입하고 Azure Monitor 경고를 받은 SRE Agent가 원인을 짚어내는지 | 배포 5분 + 시나리오당 30~40분 | 있음 (Container Apps, Log Analytics, Storage 등) | + +각 랩의 README가 사전 조건, 배포, 정리 절차를 안내합니다. **실습을 마치면 반드시 각 랩의 정리 절차를 따라 리소스를 삭제하세요.** + +새 랩을 추가할 때는 `azure.yaml`과 README를 갖춘 디렉터리를 만들고 위 표에 행을 추가합니다. + ## 📝 기여 방법 ### 새로운 가이드 추가하기 diff --git a/monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py b/monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py new file mode 100644 index 00000000..16d18613 --- /dev/null +++ b/monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py @@ -0,0 +1,116 @@ +"""Contract tests for the repository README's lab index. + +The root README is where someone arriving at this repository looks first, +so a lab that can actually be deployed and run has to be reachable from +there. These tests keep that index honest as labs are added: every runnable +lab is listed exactly once, every link resolves, and the columns a reader +uses to decide whether to start (cost, time) are filled in. + +A "runnable lab" is defined the same way the repository already +distinguishes one: a directory with its own `azure.yaml`, i.e. something +`azd up` can provision. Documentation-only guides are not labs and are not +listed here. +""" +import re +from pathlib import Path + + +REPO_ROOT = Path(__file__).parents[4] +README = REPO_ROOT / "README.md" + +LAB_SECTION_HEADING = "## 🧪 실습 랩" + +# Directories that are never scanned for labs: build output, virtual +# environments, and the worktrees this repository's own workflow creates. +IGNORED_PARTS = { + ".git", + ".worktrees", + ".venv", + "node_modules", + "__pycache__", +} + + +def runnable_labs(): + """Every directory an operator can provision with `azd up`.""" + labs = [] + for azure_yaml in REPO_ROOT.rglob("azure.yaml"): + relative = azure_yaml.relative_to(REPO_ROOT) + if IGNORED_PARTS & set(relative.parts): + continue + labs.append(relative.parent) + return sorted(labs) + + +def lab_section(): + text = README.read_text() + assert LAB_SECTION_HEADING in text, ( + "the README has no lab index; a reader cannot find what is runnable" + ) + start = text.index(LAB_SECTION_HEADING) + rest = text[start + len(LAB_SECTION_HEADING):] + end = rest.find("\n## ") + return rest if end == -1 else rest[:end] + + +def linked_paths(section): + """Every relative Markdown link target in the section.""" + return re.findall(r"\]\((?!https?://)([^)#]+)", section) + + +def test_every_runnable_lab_is_listed(): + labs = runnable_labs() + assert labs, "no runnable lab found; the discovery rule is wrong" + + section = lab_section() + for lab in labs: + assert str(lab) in section, ( + "{0} can be provisioned but is missing from the README index".format(lab) + ) + + +def test_a_lab_is_listed_only_once(): + """A duplicated row is how an index starts drifting from reality.""" + section = lab_section() + for lab in runnable_labs(): + rows = [ + line + for line in section.splitlines() + if line.startswith("|") and str(lab) in line + ] + assert len(rows) == 1, (lab, rows) + + +def test_every_link_in_the_index_resolves(): + section = lab_section() + targets = linked_paths(section) + assert targets, "the index lists no navigable entry" + for target in targets: + assert (REPO_ROOT / target.strip()).exists(), target + + +def test_each_row_states_the_cost_and_the_time_commitment(): + """Both labs cost money and take time; a reader decides on those two + facts before opening anything.""" + section = lab_section() + header = next(line for line in section.splitlines() if line.startswith("|")) + for column in ("소요", "과금"): + assert column in header, column + + rows = [ + line + for line in section.splitlines() + if line.startswith("|") and not set(line) <= set("|- ") + ][1:] + assert rows, "the index has a header but no entries" + for row in rows: + cells = [cell.strip() for cell in row.strip("|").split("|")] + assert all(cells), row + + +def test_the_index_explains_how_to_add_a_lab(): + """The list only stays current if the next person knows it exists.""" + section = lab_section() + assert "azure.yaml" in section, ( + "the section must state what qualifies as a lab, so a new one is added here" + ) From 6decb32e92074393d9da2bd7368f58b17480060b Mon Sep 17 00:00:00 2001 From: hellices Date: Sat, 15 Aug 2026 23:41:20 +0900 Subject: [PATCH 2/2] test: scan the repository once per run Every test in the module needs the same lab list, so cache the walk. Verified the cache does not weaken the guards: an unlisted lab and a broken link still fail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../scripts/tests/test_repo_readme.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py b/monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py index 16d18613..52f94401 100644 --- a/monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py +++ b/monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py @@ -12,6 +12,7 @@ listed here. """ import re +from functools import lru_cache from pathlib import Path @@ -31,15 +32,20 @@ } +@lru_cache(maxsize=1) def runnable_labs(): - """Every directory an operator can provision with `azd up`.""" + """Every directory an operator can provision with `azd up`. + + Cached: this walks the whole repository, and every test below needs the + same answer. + """ labs = [] for azure_yaml in REPO_ROOT.rglob("azure.yaml"): relative = azure_yaml.relative_to(REPO_ROOT) if IGNORED_PARTS & set(relative.parts): continue labs.append(relative.parent) - return sorted(labs) + return tuple(sorted(labs)) def lab_section():