@@ -2272,41 +2272,86 @@ def cleanup_backrest_from_cluster(cluster_json, target_json):
2272
2272
if "backrest" in node :
2273
2273
del node ["backrest" ]
2274
2274
def json_validate_add_node (data ):
2275
- """Validate the structure of a node configuration JSON file."""
2276
- required_keys = ["json_version" , "node_groups" ]
2277
- node_group_keys = [
2275
+ """
2276
+ Validate the structure of a node‑definition JSON file that will be fed to
2277
+ the add‑node command.
2278
+
2279
+ • The traditional checks (json_version, ssh, port, …) still apply.
2280
+ • A node_group is not required to have a “backrest” block.
2281
+ • If a “backrest” block is present, it must contain at least:
2282
+ • stanza – unique stanza name
2283
+ • repo1_path – absolute path to the repo directory
2284
+ • repo1_type – 'posix' or 's3'
2285
+ and the values must be non‑empty and valid.
2286
+ """
2287
+
2288
+ # ---------- top‑level keys ------------------------------------------------
2289
+ required_top = {"json_version" , "node_groups" }
2290
+ if not required_top .issubset (data ):
2291
+ util .exit_message ("Invalid add‑node JSON: missing json_version or node_groups." )
2292
+
2293
+ if str (data .get ("json_version" )) != "1.0" :
2294
+ util .exit_message ("Invalid or unsupported json_version (must be '1.0')." )
2295
+
2296
+ # ---------- per‑node_group validation ------------------------------------
2297
+ node_group_required = {
2278
2298
"ssh" ,
2279
2299
"name" ,
2280
2300
"is_active" ,
2281
2301
"public_ip" ,
2282
2302
"private_ip" ,
2283
2303
"port" ,
2284
2304
"path" ,
2285
- ]
2286
- ssh_keys = ["os_user" , "private_key" ]
2287
- if "json_version" not in data or data ["json_version" ] == "1.0" :
2288
- util .exit_message ("Invalid or missing JSON version." )
2305
+ }
2306
+ ssh_required = {"os_user" , "private_key" }
2289
2307
2290
- for key in required_keys :
2291
- if key not in data :
2292
- util .exit_message (f"Key '{ key } ' missing from JSON data." )
2308
+ backrest_required = {"stanza" , "repo1_path" , "repo1_type" }
2309
+ valid_repo1_types = {"posix" , "s3" }
2293
2310
2294
2311
for group in data ["node_groups" ]:
2295
- for node_group_key in node_group_keys :
2296
- if node_group_key not in group :
2297
- util .exit_message (f"Key '{ node_group_key } ' missing from node group." )
2312
+ gname = group .get ("name" , "?" )
2298
2313
2299
- ssh_info = group .get ("ssh" , {})
2300
- for ssh_key in ssh_keys :
2301
- if ssh_key not in ssh_info :
2302
- util .exit_message (f"Key '{ ssh_key } ' missing from ssh configuration." )
2314
+ # --- basic mandatory keys
2315
+ missing_basic = node_group_required - set (group .keys ())
2316
+ if missing_basic :
2317
+ util .exit_message (
2318
+ f"Node‑group '{ gname } ' missing keys: { ', ' .join (missing_basic )} "
2319
+ )
2303
2320
2304
- if "public_ip" not in group and "private_ip" not in group :
2321
+ # --- ssh block
2322
+ ssh_info = group ["ssh" ]
2323
+ missing_ssh = ssh_required - set (ssh_info .keys ())
2324
+ if missing_ssh :
2305
2325
util .exit_message (
2306
- "Both 'public_ip' and 'private_ip' are missing from node group. "
2326
+ f"SSH block in node‑group ' { gname } ' missing: { ', ' . join ( missing_ssh ) } "
2307
2327
)
2308
2328
2309
- util .message (f"New node json file structure is valid." , "success" )
2329
+ # --- backrest (optional but validated if present)
2330
+ if "backrest" in group and group ["backrest" ] is not None :
2331
+ br = group ["backrest" ]
2332
+
2333
+ # ensure required keys are present
2334
+ missing_br = backrest_required - set (br .keys ())
2335
+ if missing_br :
2336
+ util .exit_message (
2337
+ f"BackRest block in node‑group '{ gname } ' missing: { ', ' .join (missing_br )} "
2338
+ )
2339
+
2340
+ # ensure values are non‑empty
2341
+ for k in backrest_required :
2342
+ if not str (br [k ]).strip ():
2343
+ util .exit_message (
2344
+ f"BackRest key '{ k } ' in node‑group '{ gname } ' cannot be empty."
2345
+ )
2346
+
2347
+ # verify repo1_type is valid
2348
+ if br ["repo1_type" ] not in valid_repo1_types :
2349
+ util .exit_message (
2350
+ f"Invalid repo1_type '{ br ['repo1_type' ]} ' in node‑group '{ gname } '. "
2351
+ f"Allowed: { ', ' .join (valid_repo1_types )} "
2352
+ )
2353
+
2354
+ util .message ("✔ add‑node JSON structure is valid." , "success" )
2310
2355
2311
2356
def remove_node (cluster_name , node_name ):
2312
2357
"""Remove a node from the cluster configuration.
0 commit comments