docs(broker): establish remote Docker deployment plan for nats-server with networking/security guides and add D-15~D-21 freshness guards
This commit is contained in:
@@ -283,16 +283,11 @@ def test_d11_private_server_env_names_valid():
|
||||
code_blocks = re.findall(r"```(?:bash|conf|yaml|)(.*?)```", content, re.DOTALL)
|
||||
assert code_blocks, "No code blocks found in PRIVATE_SERVER.md"
|
||||
|
||||
# Known recognized MQTT env vars from broker_config_from_env()
|
||||
recognized = {
|
||||
"MQTT_BROKER", "MQTT_PORT", "MQTT_TLS", "MQTT_USERNAME", "MQTT_PASSWORD",
|
||||
"MQTT_CLIENT_ID_PREFIX", "MQTT_CA_CERTS", "MQTT_CERTFILE", "MQTT_KEYFILE",
|
||||
"MQTT_KEEPALIVE", "MAM_MQTT_HOST" # checked for exclusion
|
||||
}
|
||||
# Known recognized MQTT env vars from broker_config_from_env() and deployment
|
||||
valid_mqtt_vars = {
|
||||
"MQTT_BROKER", "MQTT_PORT", "MQTT_TLS", "MQTT_USERNAME", "MQTT_PASSWORD",
|
||||
"MQTT_CLIENT_ID_PREFIX", "MQTT_CA_CERTS", "MQTT_CERTFILE", "MQTT_KEYFILE",
|
||||
"MQTT_KEEPALIVE"
|
||||
"MQTT_KEEPALIVE", "MQTT_BIND"
|
||||
}
|
||||
|
||||
for block in code_blocks:
|
||||
@@ -352,3 +347,117 @@ def test_d14_private_server_cli_args_valid():
|
||||
)
|
||||
assert "status --job " in code_blocks, "PRIVATE_SERVER.md must include cleanup step with status --job"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# D-15 — (G-D5) store_dir in code blocks must be absolute path (/ or $HOME)
|
||||
# and heredocs writing it must be unquoted (<<EOF).
|
||||
# --------------------------------------------------------------------------
|
||||
def test_d15_private_server_store_dir_valid():
|
||||
doc_path = os.path.join(REPO_ROOT, "PRIVATE_SERVER.md")
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
code_blocks = re.findall(r"```(?:bash|conf|yaml|)(.*?)```", content, re.DOTALL)
|
||||
for i, block in enumerate(code_blocks):
|
||||
for match in re.finditer(r'store_dir:\s*["\']?([^"\'\n]+)["\']?', block):
|
||||
val = match.group(1).strip()
|
||||
assert val.startswith("/") or val.startswith("$HOME"), (
|
||||
f"Block #{i+1} store_dir '{val}' must start with '/' or '$HOME' (no literal ~)"
|
||||
)
|
||||
if "store_dir:" in block and "cat <<" in block:
|
||||
assert "<<'EOF'" not in block, (
|
||||
f"Block #{i+1} writes store_dir with quoted heredoc <<'EOF', which prevents $HOME expansion"
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# D-16 — (G-D6) nats image references in code fences must use pinned alpine
|
||||
# --------------------------------------------------------------------------
|
||||
def test_d16_private_server_nats_image_alpine_pinned():
|
||||
doc_path = os.path.join(REPO_ROOT, "PRIVATE_SERVER.md")
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
code_blocks = re.findall(r"```(?:bash|conf|yaml|)(.*?)```", content, re.DOTALL)
|
||||
for i, block in enumerate(code_blocks):
|
||||
nats_refs = re.findall(r'\bnats:([a-zA-Z0-9_.-]+)', block)
|
||||
for tag in nats_refs:
|
||||
assert tag != "latest", f"Block #{i+1} contains unpinned 'nats:latest'"
|
||||
assert "-alpine" in tag or tag.startswith("2."), f"Block #{i+1} nats image '{tag}' must use alpine variant"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# D-17 — (G-D7) Port 8222 in docker examples must be bound to 127.0.0.1
|
||||
# --------------------------------------------------------------------------
|
||||
def test_d17_private_server_monitoring_port_localhost_bound():
|
||||
doc_path = os.path.join(REPO_ROOT, "PRIVATE_SERVER.md")
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
code_blocks = re.findall(r"```(?:bash|conf|yaml|)(.*?)```", content, re.DOTALL)
|
||||
for i, block in enumerate(code_blocks):
|
||||
for match in re.finditer(r'["\']?([0-9a-zA-Z._$:-]*8222:8222)["\']?', block):
|
||||
mapping = match.group(1).strip()
|
||||
assert "127.0.0.1:8222:8222" in mapping, (
|
||||
f"Block #{i+1} port 8222 must be bound to 127.0.0.1, got '{mapping}'"
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# D-18 — (G-D8) TLS examples must not use IP literals for MQTT_BROKER
|
||||
# --------------------------------------------------------------------------
|
||||
def test_d18_private_server_tls_examples_use_domain_names():
|
||||
doc_path = os.path.join(REPO_ROOT, "PRIVATE_SERVER.md")
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
code_blocks = re.findall(r"```(?:bash|conf|yaml|)(.*?)```", content, re.DOTALL)
|
||||
for i, block in enumerate(code_blocks):
|
||||
if "MQTT_TLS=1" in block or "port: 8883" in block:
|
||||
for match in re.finditer(r'MQTT_BROKER=["\']?([0-9.]+)', block):
|
||||
ip = match.group(1)
|
||||
assert False, f"Block #{i+1} uses IP literal '{ip}' with TLS (must use DNS domain name for SAN verification)"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# D-19 — (G-D9) Subject literals in config examples match DEFAULT_TOPIC_ROOT
|
||||
# --------------------------------------------------------------------------
|
||||
def test_d19_private_server_subject_literals_match_default_topic_root():
|
||||
doc_path = os.path.join(REPO_ROOT, "PRIVATE_SERVER.md")
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
sys.path.insert(0, os.path.join(REPO_ROOT, ".agents", "skills", "multi-agent-mux-delegate-job", "scripts"))
|
||||
import mqtt_common
|
||||
expected_prefix = mqtt_common.DEFAULT_TOPIC_ROOT.replace("/", ".")
|
||||
matches = re.findall(r'["\'](python\.mqtt\.jobs\.[>*\w.]+)["\']', content)
|
||||
assert matches, "Expected subject literals matching DEFAULT_TOPIC_ROOT in PRIVATE_SERVER.md"
|
||||
for sub in matches:
|
||||
assert sub.startswith(expected_prefix), f"Subject '{sub}' does not start with expected prefix '{expected_prefix}'"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# D-20 — (G-R1) run_loop.sh exports MAM_ENV_FILE
|
||||
# --------------------------------------------------------------------------
|
||||
def test_d20_run_loop_exports_mam_env_file():
|
||||
run_loop_path = os.path.join(REPO_ROOT, ".agents", "skills", "multi-agent-mux-loop", "scripts", "run_loop.sh")
|
||||
with open(run_loop_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
assert 'export MAM_ENV_FILE=' in content, "run_loop.sh must export MAM_ENV_FILE"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# D-21 — (G-R2) MQTT_KEEPALIVE documented and no un-commented retry vars
|
||||
# --------------------------------------------------------------------------
|
||||
def test_d21_env_template_mqtt_var_coverage():
|
||||
template_path = os.path.join(REPO_ROOT, ".mam.env.example")
|
||||
with open(template_path, "r", encoding="utf-8") as f:
|
||||
template = f.read()
|
||||
assert "MQTT_KEEPALIVE" in template, ".mam.env.example must document MQTT_KEEPALIVE"
|
||||
for line in template.splitlines():
|
||||
line = line.strip()
|
||||
if not line.startswith("#"):
|
||||
assert "MQTT_RETRY_INTERVAL" not in line
|
||||
assert "MQTT_MAX_RETRIES" not in line
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user