add: devops scripts and configs

This commit is contained in:
“Naeel”
2026-06-30 15:45:16 +04:00
parent ccf5020e78
commit 540c1f7293
28 changed files with 2210 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""
Дополнительный генератор: обогащает *_outputs.md реальными ключами
из output_inventory (собранного со стендов).
Обновляет только секцию "## Реальные поля из облака" — не трогает остальное.
Запуск (из корня репо на VM):
python3 devops/04b_enrich_outputs_from_inventory.py [--dry-run]
"""
import json, yaml, glob, os, sys
BASE = "/home/naeel/terra/terraform"
YAML_DIR = f"{BASE}/devops/profiles/test/generated/resources_yaml"
INV_DIR = f"{BASE}/devops/profiles/test/generated/output_inventory"
DOCS_DIR = f"{BASE}/docs/30_registry/resources"
SECTION_MARKER = "## Реальные поля из облака"
def flatten(obj, prefix=""):
"""Рекурсивно собирает dot-notation ключи из вложенного dict/list."""
keys = []
if isinstance(obj, dict):
for k, v in obj.items():
full = f"{prefix}.{k}" if prefix else k
keys.append(full)
if isinstance(v, (dict, list)):
keys.extend(flatten(v, full))
elif isinstance(obj, list):
for i, v in enumerate(obj):
full = f"{prefix}.{i}" if prefix else str(i)
if isinstance(v, (dict, list)):
keys.extend(flatten(v, full))
# скалярные элементы списка не добавляем — нет смысла
return keys
def best_instance(instances):
"""Выбираем лучший инстанс: running с данными > suspended с данными."""
candidates = [i for i in instances if i.get("out") or i.get("params")]
if not candidates:
return None
running = [i for i in candidates if i.get("status") == "running"]
return running[-1] if running else candidates[-1]
def build_section(inst):
params = inst.get("params") or {}
out = inst.get("out") or {}
vault = inst.get("vault_keys") or []
source = inst.get("source", "test")
name = inst.get("name", "?")
status = inst.get("status", "?")
lines = [
f"{SECTION_MARKER} ({source} / {name} / {status})\n",
"\n",
"Поля ниже получены из реальных инстансов этого сервиса в облаке.\n",
"Используйте их как готовые ключи для `state_out_flat` и `vault_secrets`.\n",
]
if params:
lines.append("\n### `state_params` ключи\n\n")
for k in params.keys():
lines.append(f'- `state_params["{k}"]`\n')
flat_out = flatten(out)
if flat_out:
lines.append("\n### `state_out_flat` ключи\n\n")
for k in flat_out:
lines.append(f'- `state_out_flat["{k}"]`\n')
if vault:
lines.append("\n### `vault_secrets` ключи\n\n")
for k in vault:
lines.append(f'- `vault_secrets["{k}"]`\n')
return "".join(lines)
def main():
dry_run = "--dry-run" in sys.argv
# 1. service_id → name (из YAML)
svc_to_name = {}
for f in glob.glob(f"{YAML_DIR}/*.yaml"):
try:
d = yaml.safe_load(open(f))
sid = d.get("service_id")
name = d.get("name") or d.get("service_short_name")
if sid and name:
svc_to_name[sid] = name
except Exception:
pass
updated = 0
skipped = 0
for inv_file in sorted(glob.glob(f"{INV_DIR}/svc_*.json")):
if "index" in inv_file:
continue
inv = json.load(open(inv_file))
svc_id = inv.get("serviceId")
inst = best_instance(inv.get("instances", []))
if not inst:
print(f" skip svc={svc_id}: нет данных")
skipped += 1
continue
name = svc_to_name.get(svc_id)
if not name:
print(f" skip svc={svc_id}: нет YAML маппинга")
skipped += 1
continue
out_path = f"{DOCS_DIR}/{name}_outputs.md"
if not os.path.exists(out_path):
print(f" skip svc={svc_id} ({name}): нет файла {name}_outputs.md")
skipped += 1
continue
new_section = build_section(inst)
content = open(out_path).read()
if SECTION_MARKER in content:
idx = content.index(SECTION_MARKER)
new_content = content[:idx] + new_section
else:
new_content = content.rstrip("\n") + "\n\n" + new_section
if dry_run:
print(f" dry-run svc={svc_id} ({name}): {out_path}")
print(f" params={list((inst.get('params') or {}).keys())}")
print(f" out_flat_count={len(flatten(inst.get('out') or {}))}")
print(f" vault={inst.get('vault_keys', [])}")
else:
with open(out_path, "w") as f:
f.write(new_content)
print(f" updated svc={svc_id} ({name})")
updated += 1
print(f"\nДone: {updated} updated, {skipped} skipped")
if __name__ == "__main__":
main()