v1.1.25: full Terraform parity — normalize, resolveRefSvc, send ALL params, 30min timeout, svc in fields
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@ from routes.api import bp as api_bp
|
|||||||
from routes.api_test import bp as api_test_bp
|
from routes.api_test import bp as api_test_bp
|
||||||
|
|
||||||
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
|
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
|
||||||
VERSION = "1.1.24"
|
VERSION = "1.1.25"
|
||||||
|
|
||||||
app = Flask(__name__, template_folder="templates", static_folder="static")
|
app = Flask(__name__, template_folder="templates", static_folder="static")
|
||||||
app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc")
|
app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc")
|
||||||
|
|||||||
+104
-46
@@ -199,47 +199,8 @@ def api_test():
|
|||||||
print(f"[TRACKER ERROR] add failed: {e}", flush=True)
|
print(f"[TRACKER ERROR] add failed: {e}", flush=True)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
# Отправка пользовательских параметров
|
# Отправка параметров (шаги 3-7 Terraform)
|
||||||
for pid, pval in params.items():
|
_send_params_terraform(client, op_uid, params)
|
||||||
if pval: # только непустые
|
|
||||||
client.post("/instanceOperationCfsParams",
|
|
||||||
{"instanceOperationUid": op_uid, "svcOperationCfsParamId": int(pid), "paramValue": str(pval)})
|
|
||||||
|
|
||||||
# Досылка required-параметров (шаг 6 Terraform)
|
|
||||||
# Берём из РЕАЛЬНОЙ операции — не из шаблона (там нет paramValue)
|
|
||||||
try:
|
|
||||||
op_det = client.get(f"/instanceOperations/{op_uid}?fields=cfsParams")
|
|
||||||
for p in op_det.get("instanceOperation", {}).get("cfsParams", []):
|
|
||||||
pid_str = str(p["svcOperationCfsParamId"])
|
|
||||||
if p.get("isRequired") and pid_str not in params:
|
|
||||||
val = p.get("paramValue") or p.get("defaultValue")
|
|
||||||
dd = p.get("dataDescriptor")
|
|
||||||
if val is not None and val != "":
|
|
||||||
client.post("/instanceOperationCfsParams", {
|
|
||||||
"instanceOperationUid": op_uid,
|
|
||||||
"svcOperationCfsParamId": p["svcOperationCfsParamId"],
|
|
||||||
"paramValue": str(val)
|
|
||||||
})
|
|
||||||
elif isinstance(dd, dict):
|
|
||||||
sub_obj = {}
|
|
||||||
for sk, sv in dd.items():
|
|
||||||
sd = sv.get("paramValue") or sv.get("defaultValue", "")
|
|
||||||
if sv.get("isRequired") and sd:
|
|
||||||
sub_obj[sk] = sd
|
|
||||||
if sub_obj:
|
|
||||||
client.post("/instanceOperationCfsParams", {
|
|
||||||
"instanceOperationUid": op_uid,
|
|
||||||
"svcOperationCfsParamId": p["svcOperationCfsParamId"],
|
|
||||||
"paramValue": json.dumps(sub_obj)
|
|
||||||
})
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Валидация перед запуском
|
|
||||||
try:
|
|
||||||
client.get(f"/instanceOperations/{op_uid}/validate-cfs")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
client.post(f"/instanceOperations/{op_uid}/run")
|
client.post(f"/instanceOperations/{op_uid}/run")
|
||||||
|
|
||||||
@@ -266,9 +227,7 @@ def api_test():
|
|||||||
op_uid = _find_uid(op_resp) or _uid_from_location(op_resp.get("_location", ""))
|
op_uid = _find_uid(op_resp) or _uid_from_location(op_resp.get("_location", ""))
|
||||||
if not op_uid:
|
if not op_uid:
|
||||||
return jsonify({"status": "FAIL", "error": "Не удалось получить opUid"}), 500
|
return jsonify({"status": "FAIL", "error": "Не удалось получить opUid"}), 500
|
||||||
for pid, pval in params.items():
|
_send_params_terraform(client, op_uid, params)
|
||||||
client.post("/instanceOperationCfsParams",
|
|
||||||
{"instanceOperationUid": op_uid, "svcOperationCfsParamId": int(pid), "paramValue": str(pval)})
|
|
||||||
client.post(f"/instanceOperations/{op_uid}/run")
|
client.post(f"/instanceOperations/{op_uid}/run")
|
||||||
|
|
||||||
# фоном ждать завершения
|
# фоном ждать завершения
|
||||||
@@ -291,6 +250,105 @@ _op_results = {}
|
|||||||
_MAX_OP_RESULTS = 500
|
_MAX_OP_RESULTS = 500
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
# map-fixed/map с DataDescriptor: пусто или {} → собрать из sub-param defaults
|
||||||
|
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 # пустая строка — отправляем как есть (Terraform тоже шлёт)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_ref_svc(client, cfs_params, params):
|
||||||
|
"""resolveRefSvcParamValues — Terraform step 4.
|
||||||
|
Для параметров с refSvcId в dataDescriptor: если значение пустое —
|
||||||
|
подставить UUID существующего инстанса этого сервиса."""
|
||||||
|
instances = None # lazy load
|
||||||
|
for p in cfs_params:
|
||||||
|
dd = p.get("dataDescriptor")
|
||||||
|
if not isinstance(dd, dict):
|
||||||
|
continue
|
||||||
|
pid = str(p["svcOperationCfsParamId"])
|
||||||
|
user_val = json.loads(params.get(pid, "{}")) if pid in params else {}
|
||||||
|
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 and pid in params:
|
||||||
|
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.
|
||||||
|
Работает для CREATE и non-CREATE одинаково."""
|
||||||
|
# Шаг 3: GET cfsParams реальной операции
|
||||||
|
op_det = client.get(f"/instanceOperations/{op_uid}?fields=cfsParams")
|
||||||
|
cfs_params = op_det.get("instanceOperation", {}).get("cfsParams", [])
|
||||||
|
# Шаг 4: resolveRefSvcParamValues
|
||||||
|
_resolve_ref_svc(client, cfs_params, params)
|
||||||
|
# Шаг 5: отправить ВСЕ пользовательские 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))
|
||||||
|
# Шаг 6: дослать ВСЕ неотправленные params из cfsParams
|
||||||
|
for p in cfs_params:
|
||||||
|
pid = p["svcOperationCfsParamId"]
|
||||||
|
if pid in sent:
|
||||||
|
continue
|
||||||
|
# nil-check: paramValue может быть None (отличаем от "")
|
||||||
|
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
|
||||||
|
})
|
||||||
|
# Шаг 7: validate-cfs (ошибка НЕ глотается — возвращается вызывающему)
|
||||||
|
client.get(f"/instanceOperations/{op_uid}/validate-cfs")
|
||||||
|
|
||||||
|
|
||||||
def _cleanup_op_results():
|
def _cleanup_op_results():
|
||||||
"""Удалить старые записи: старше 1 часа или сверх лимита."""
|
"""Удалить старые записи: старше 1 часа или сверх лимита."""
|
||||||
import time
|
import time
|
||||||
@@ -321,11 +379,11 @@ def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_
|
|||||||
import time
|
import time
|
||||||
_cleanup_op_results() # очистить старые записи перед добавлением новой
|
_cleanup_op_results() # очистить старые записи перед добавлением новой
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
deadline = t0 + 300
|
deadline = t0 + 1800
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
data = client.get(f"/instanceOperations/{op_uid}?fields=dtFinish,isSuccessful,errorLog,isInProgress,duration,stages")
|
data = client.get(f"/instanceOperations/{op_uid}?fields=dtFinish,isSuccessful,errorLog,isInProgress,duration,stages,svc")
|
||||||
except Exception:
|
except Exception:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user