Backend (5→7 files): - api_test.py: 622→479, terraform functions → operations/terraform.py (142) - api_scenario.py: 241→split into run (153) + defs (108) with _validate_steps() - db/scenario_defs.py: +client_id, stand, is_seed in list_definitions Frontend (1→10 files): - app.js: 554→16 (loader), split into: utils.js (45), instances.js (89), operations.js (249), history.js (35), scenario-list.js (90) - New CRUD: scenario-form.js (128), create (25), edit (12), delete (13) Max file size: JS 249, PY 479
143 lines
5.2 KiB
Python
143 lines
5.2 KiB
Python
"""
|
|
Terraform-совместимые операции: нормализация параметров, refSvc-резолв, отправка.
|
|
|
|
Используется из api_test.py и scenario.py.
|
|
"""
|
|
|
|
import json
|
|
from operations.get_instances import get_instances
|
|
|
|
REDACT_KEYS = {"password", "secret", "token", "key", "privatekey", "private_key", "passwd", "pass"}
|
|
|
|
|
|
def redact_params(params):
|
|
"""Заменить значения секретных полей на *** перед сохранением в БД."""
|
|
if not params:
|
|
return params
|
|
result = {}
|
|
for k, v in params.items():
|
|
if any(r in str(k).lower() for r in REDACT_KEYS):
|
|
result[k] = "***"
|
|
else:
|
|
result[k] = v
|
|
return result
|
|
|
|
|
|
def normalize_value(val, data_type, data_descriptor):
|
|
"""normalizeUniversalValueV6 — Terraform-equivalent.
|
|
Пустые значения нормализуются по типу: integer→"0", boolean→"false",
|
|
array→"[]", map/json→"{}", map-fixed→сборка из sub-param defaults."""
|
|
val = (val or "").strip()
|
|
if val.lower() == "null" or val == '""':
|
|
val = ""
|
|
dt = (data_type or "").lower()
|
|
if ("map" in dt) and isinstance(data_descriptor, dict) and data_descriptor:
|
|
if not val or val == "{}":
|
|
sub_obj = {}
|
|
for sk, sv in data_descriptor.items():
|
|
sd = sv.get("defaultValue")
|
|
sub_obj[sk] = str(sd) if sd is not None else ""
|
|
return json.dumps(sub_obj)
|
|
return val
|
|
if val:
|
|
return val
|
|
if "array" in dt:
|
|
return "[]"
|
|
if "map" in dt or "json" in dt:
|
|
return "{}"
|
|
if "integer" in dt or "int" in dt:
|
|
return "0"
|
|
if "boolean" in dt or "bool" in dt:
|
|
return "false"
|
|
return val
|
|
|
|
|
|
def resolve_ref_svc(client, cfs_params, params):
|
|
"""resolveRefSvcParamValues — Terraform step 4.
|
|
Для параметров с refSvcId: если значение пустое — подставить UUID
|
|
существующего инстанса этого сервиса."""
|
|
instances = None
|
|
for p in cfs_params:
|
|
pid = str(p["svcOperationCfsParamId"])
|
|
# Верхнеуровневый refSvcId
|
|
top_ref = p.get("refSvcId")
|
|
if top_ref:
|
|
cur = (params.get(pid) or "").strip()
|
|
if not cur or cur == '""':
|
|
if instances is None:
|
|
try:
|
|
instances = get_instances(client)
|
|
except Exception:
|
|
return
|
|
match = next((i for i in instances
|
|
if i.get("serviceId") == top_ref
|
|
and i.get("explainedStatus") not in ("deleted",)), None)
|
|
if match:
|
|
params[pid] = match["instanceUid"]
|
|
# Вложенный refSvcId в dataDescriptor
|
|
dd = p.get("dataDescriptor")
|
|
if not isinstance(dd, dict):
|
|
continue
|
|
user_val = {}
|
|
if pid in params:
|
|
try:
|
|
user_val = json.loads(params[pid]) if params[pid] else {}
|
|
except (json.JSONDecodeError, ValueError):
|
|
user_val = {}
|
|
for sk, sv in dd.items():
|
|
ref_id = sv.get("refSvcId")
|
|
if not ref_id:
|
|
continue
|
|
cur = user_val.get(sk, "")
|
|
if cur and cur != '""':
|
|
continue
|
|
if instances is None:
|
|
try:
|
|
instances = get_instances(client)
|
|
except Exception:
|
|
return
|
|
match = next((i for i in instances
|
|
if i.get("serviceId") == ref_id
|
|
and i.get("explainedStatus") not in ("deleted",)), None)
|
|
if match:
|
|
user_val[sk] = match["instanceUid"]
|
|
if user_val:
|
|
params[pid] = json.dumps(user_val)
|
|
|
|
|
|
def send_params_terraform(client, op_uid, params):
|
|
"""Terraform steps 3-7: cfsParams → resolveRefSvc → send user → send all unsent → validate."""
|
|
op_det = client.get(f"/instanceOperations/{op_uid}?fields=cfsParams")
|
|
cfs_params = op_det.get("instanceOperation", {}).get("cfsParams", [])
|
|
resolve_ref_svc(client, cfs_params, params)
|
|
sent = set()
|
|
for pid, pval in params.items():
|
|
client.post("/instanceOperationCfsParams", {
|
|
"instanceOperationUid": op_uid,
|
|
"svcOperationCfsParamId": int(pid),
|
|
"paramValue": str(pval)
|
|
})
|
|
sent.add(int(pid))
|
|
for p in cfs_params:
|
|
pid = p["svcOperationCfsParamId"]
|
|
if pid in sent:
|
|
continue
|
|
val = p.get("paramValue")
|
|
if val is None:
|
|
val = p.get("defaultValue")
|
|
if val is None:
|
|
val = ""
|
|
val = normalize_value(str(val), p.get("dataType", ""), p.get("dataDescriptor"))
|
|
client.post("/instanceOperationCfsParams", {
|
|
"instanceOperationUid": op_uid,
|
|
"svcOperationCfsParamId": pid,
|
|
"paramValue": val
|
|
})
|
|
try:
|
|
client.get(f"/instanceOperations/{op_uid}/validate-cfs")
|
|
except Exception as e:
|
|
if "Expecting value" in str(e) or "JSON" in str(type(e).__name__):
|
|
pass
|
|
else:
|
|
raise
|