Files
polygon/site/from_stands.py
T

347 lines
11 KiB
Python
Raw 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.
"""
from_stands.py — конвертер STANDS YAML → polygon service config.
Читает YAML из STANDS_DIR, генерирует конфиги в SERVICES_DIR.
Запуск: python from_stands.py <STANDS_DIR> [SERVICES_DIR]
"""
import html
import os
import sys
import yaml
# Расширение для YAML-файлов (может быть .yaml или .yml)
YAML_EXTS = (".yaml", ".yml")
# Поля которые переносятся 1:1 из STANDS YAML
TOP_LEVEL_KEYS = (
"name", "service_id", "service_display_name",
"service_short_name", "service_man",
)
# Стандартные 6 операций инстанса
INSTANCE_OPERATIONS = frozenset({
"create", "modify", "delete", "suspend", "resume", "redeploy",
})
# ---------------------------------------------------------------------------
# Утилиты
# ---------------------------------------------------------------------------
def _ue(s):
"""html.unescape строки."""
if isinstance(s, str):
return html.unescape(s)
return s
def _pluralize(word):
"""Простейшая плюрализация: user→users, database→databases."""
if word.endswith("s"):
return word
if word.endswith("y") and word[-2] not in "aeiou":
return word[:-1] + "ies"
return word + "s"
# ---------------------------------------------------------------------------
# Конвертация параметров
# ---------------------------------------------------------------------------
def _convert_param(p, is_modifiable_default=False):
"""Конвертирует один параметр STANDS → polygon cfsParam.
Возвращает dict с полями:
svcOperationCfsParamId, svcOperationCfsParam, dataType,
isRequired, defaultValue, valueList, isModifiable,
dataDescriptor (если есть sub_params)
"""
dt = _ue(p.get("data_type", "string"))
required = p.get("required", False)
default = _ue(str(p.get("default", "")))
is_modifiable = p.get("is_modifiable", is_modifiable_default)
out = {
"svcOperationCfsParamId": p["id"],
"svcOperationCfsParam": p["code"],
"dataType": dt,
"isRequired": required,
"defaultValue": default,
"isModifiable": is_modifiable,
}
# valueList — может быть None, тогда не включаем (input, не select)
vl = p.get("value_list")
if vl is not None:
out["valueList"] = [_ue(str(v)) for v in vl]
# refSvcId
ref = p.get("refSvcId")
if ref is not None:
out["refSvcId"] = ref
# dataDescriptor из sub_params
sub = p.get("sub_params")
if sub:
out["dataDescriptor"] = _convert_sub_params(sub)
return out
def _convert_sub_params(sub_params):
"""Конвертирует sub_params → dataDescriptor.
Формат выхода:
{code: {dataType, defaultValue, isRequired, valueList?, isModifiable?}}
"""
dd = {}
for sp in sub_params:
code = sp["code"]
dt = _ue(sp.get("data_type", "string"))
default = _ue(str(sp.get("default", "")))
entry = {
"dataType": dt,
"defaultValue": default,
"isRequired": sp.get("required", False),
"isModifiable": sp.get("is_modifiable", False),
}
vl = sp.get("value_list")
if vl is not None:
entry["valueList"] = [_ue(str(v)) for v in vl]
# Рекурсия: sub_params внутри sub_params (редко, но возможно)
subsub = sp.get("sub_params")
if subsub:
entry["dataDescriptor"] = _convert_sub_params(subsub)
dd[code] = entry
return dd
# ---------------------------------------------------------------------------
# Конвертация операций
# ---------------------------------------------------------------------------
def _convert_operations(operations):
"""Конвертирует список операций STANDS → polygon.
Возвращает (ops_list, state_out_template):
ops_list — список operation-диктов
state_out_template — {plural: {}} для subresource create-операций
"""
ops_list = []
state_out_template = {}
for op in operations:
op_id = op["id"]
op_name = op.get("name", "")
op_kind = op.get("kind", "instance")
op_action = op.get("action", "")
ops_list.append({
"svcOperationId": op_id,
"operation": op_name,
"kind": op_kind,
"action": op_action,
})
# subresource create → добавить в state_out_template
if op_kind == "subresource" and op_action == "create":
# subresource имя из operation name: create_user → user
sub_name = op_name.replace("create_", "")
plural = _pluralize(sub_name)
state_out_template[plural] = {}
return ops_list, state_out_template
# ---------------------------------------------------------------------------
# Конвертация одного сервиса
# ---------------------------------------------------------------------------
def convert_service(stands_yaml_path):
"""Читает STANDS YAML, возвращает polygon-конфиг как dict."""
with open(stands_yaml_path, "r", encoding="utf-8") as f:
raw = yaml.safe_load(f)
if not raw:
return None
out = {}
# 1. Поля верхнего уровня (1:1)
for key in TOP_LEVEL_KEYS:
val = raw.get(key)
if val is not None:
if isinstance(val, str):
val = _ue(val)
out[key] = val
# 2. lifecycle
lc = raw.get("lifecycle", {})
if lc:
out["lifecycle"] = {
"suspendOnDestroyDefault": lc.get("suspend_on_destroy_default", False),
"adoptExistingOnCreateDefault": lc.get("adopt_existing_on_create_default", False),
}
# 3. Операции
operations = raw.get("operations", [])
ops_list, state_out_template = _convert_operations(operations)
out["operations"] = ops_list
out["stateOut"] = state_out_template
# 4. cfsParams — собираем из ВСЕХ операций, dedup по svcOperationCfsParamId
cfs_params = {}
cfs_params_by_op = {} # op_id → [param, ...]
for op in operations:
op_id = op["id"]
params_for_op = []
for p in op.get("params", []):
pid = p["id"]
if pid not in cfs_params:
is_mod = p.get("is_modifiable", op.get("action") != "create")
cfs_params[pid] = _convert_param(p, is_modifiable_default=is_mod)
params_for_op.append(pid)
cfs_params_by_op[op_id] = params_for_op
out["cfsParams"] = list(cfs_params.values())
out["cfsParamsByOp"] = cfs_params_by_op
# 5. stateParams — дефолты из create-операции (включая map-fixed)
state_params = {}
create_op = None
for op in operations:
if op.get("action") == "create" and op.get("kind") == "instance":
create_op = op
break
if create_op:
for p in create_op.get("params", []):
code = p["code"]
default = _ue(str(p.get("default", "")))
dt = _ue(p.get("data_type", "string"))
sub = p.get("sub_params")
if sub:
# map-fixed / map с sub_params — генерируем JSON из defaults
state_params[code] = _build_default_json(sub)
else:
state_params[code] = default
out["stateParams"] = state_params
return out
def _build_default_json(sub_params):
"""Строит JSON-строку из defaults sub_params."""
parts = []
for sp in sub_params:
code = sp["code"]
default = _ue(str(sp.get("default", "")))
dt = _ue(sp.get("data_type", "string"))
subsub = sp.get("sub_params")
if subsub:
val = _build_default_json(subsub)
elif "bool" in dt:
val = default.lower() if default.lower() in ("true", "false") else "false"
elif "int" in dt or "integer" in dt:
try:
int(default)
val = default
except (ValueError, TypeError):
val = "0"
else:
# string — quoted
escaped = default.replace('"', '\\"')
val = f'"{escaped}"'
parts.append(f'"{code}": {val}')
return "{" + ", ".join(parts) + "}"
# ---------------------------------------------------------------------------
# Точка входа
# ---------------------------------------------------------------------------
def main():
if len(sys.argv) < 2:
print("Usage: python from_stands.py <STANDS_DIR> [SERVICES_DIR]")
print(" STANDS_DIR — путь к STANDS YAML (напр. ../STANDS/test/resources_yaml)")
print(" SERVICES_DIR — путь для выхода (по умолчанию ./services)")
sys.exit(1)
stands_dir = sys.argv[1]
services_dir = sys.argv[2] if len(sys.argv) > 2 else os.path.join(os.path.dirname(__file__) or ".", "services")
if not os.path.isdir(stands_dir):
print(f"ERROR: STANDS_DIR not found: {stands_dir}")
sys.exit(1)
os.makedirs(services_dir, exist_ok=True)
# Найти все YAML-файлы
yaml_files = sorted(
f for f in os.listdir(stands_dir)
if f.lower().endswith(YAML_EXTS)
)
if not yaml_files:
print(f"No YAML files found in {stands_dir}")
sys.exit(1)
print(f"Found {len(yaml_files)} YAML files in {stands_dir}")
print(f"Output: {services_dir}/")
print()
converted = 0
errors = 0
for fname in yaml_files:
src = os.path.join(stands_dir, fname)
dst = os.path.join(services_dir, fname)
try:
cfg = convert_service(src)
if cfg is None:
print(f" SKIP {fname}: empty YAML")
continue
with open(dst, "w", encoding="utf-8") as f:
yaml.dump(
cfg, f,
allow_unicode=True,
default_flow_style=False,
sort_keys=False,
width=200,
)
# Быстрая проверка результата
svc_name = cfg.get("name", "?")
ops_count = len(cfg.get("operations", []))
params_count = len(cfg.get("cfsParams", []))
state_out = list(cfg.get("stateOut", {}).keys())
subresources = " +".join(state_out) if state_out else "—"
print(f" OK {fname:40s} svc={svc_name:20s} ops={ops_count} params={params_count} stateOut={subresources}")
converted += 1
except Exception as e:
print(f" FAIL {fname}: {e}")
errors += 1
print()
print(f"Done: {converted} converted, {errors} errors")
if __name__ == "__main__":
main()