3333 requires_environments ,
3434 retrieve_workflow_logs ,
3535 follow_workflow_logs ,
36+ parse_workflow_run_number ,
37+ get_run_number_major_key ,
38+ format_run_number_label ,
39+ format_run_label_list ,
3640)
3741from reana_client .config import (
3842 ERROR_MESSAGES ,
3943 RUN_STATUSES ,
4044 TIMECHECK ,
4145 CLI_LOGS_FOLLOW_DEFAULT_INTERVAL ,
46+ MAX_RUN_LABELS_SHOWN ,
47+ CLI_WORKFLOWS_LIST_MAX_RESULTS ,
4248)
4349from reana_client .printer import display_message
4450from reana_client .utils import (
@@ -1261,6 +1267,15 @@ def workflow_run(
12611267 is_flag = True ,
12621268 help = "Delete all runs of a given workflow." ,
12631269)
1270+ @click .option (
1271+ "--include-all-restarts" ,
1272+ "include_all_restarts" ,
1273+ is_flag = True ,
1274+ help = (
1275+ "Delete all restarted runs that share the same workspace as the selected run. "
1276+ "Without this flag, deletion will fail if the run is part of a restart chain."
1277+ ),
1278+ )
12641279@click .option (
12651280 "--include-workspace" ,
12661281 "should_delete_workspace" ,
@@ -1272,7 +1287,12 @@ def workflow_run(
12721287@check_connection
12731288@click .pass_context
12741289def workflow_delete (
1275- ctx , workflow : str , all_runs : bool , should_delete_workspace : bool , access_token : str
1290+ ctx ,
1291+ workflow : str ,
1292+ all_runs : bool ,
1293+ include_all_restarts : bool ,
1294+ should_delete_workspace : bool ,
1295+ access_token : str ,
12761296): # noqa: D301
12771297 """Delete a workflow.
12781298
@@ -1285,7 +1305,11 @@ def workflow_delete(
12851305 \t $ reana-client delete -w myanalysis.42\n
12861306 \t $ reana-client delete -w myanalysis.42 --include-all-runs
12871307 """
1288- from reana_client .api .client import delete_workflow
1308+ from reana_client .api .client import (
1309+ delete_workflow ,
1310+ get_workflow_status ,
1311+ get_workflows ,
1312+ )
12891313 from reana_client .utils import get_api_url
12901314
12911315 should_delete_workspace = True
@@ -1297,14 +1321,119 @@ def workflow_delete(
12971321 if workflow :
12981322 try :
12991323 logging .info ("Connecting to {0}" .format (get_api_url ()))
1300- delete_workflow (workflow , all_runs , should_delete_workspace , access_token )
1324+
1325+ # If deleting all runs, keep existing behaviour (including restarts)
13011326 if all_runs :
1327+ # Check if any run for given workflow exists
1328+ wf_status = get_workflow_status (workflow , access_token ) or {}
1329+ full_name = wf_status .get ("name" ) or workflow
1330+ base_name , _ , _ = parse_workflow_run_number (full_name )
1331+ workflow_base = base_name or full_name
1332+ status_filter = RUN_STATUSES .copy ()
1333+ if "deleted" in status_filter :
1334+ status_filter .remove ("deleted" )
1335+ runs = get_workflows (
1336+ access_token = access_token ,
1337+ type = "batch" ,
1338+ page = 1 ,
1339+ size = 1 , # we only need to know if there is at least one
1340+ status = status_filter ,
1341+ workflow = workflow_base ,
1342+ )
1343+ if not runs :
1344+ display_message (
1345+ f"All runs of '{ workflow_base } ' are already deleted." ,
1346+ msg_type = "info" ,
1347+ )
1348+ return
1349+
1350+ # Delete all runs
1351+ delete_workflow (
1352+ workflow , all_runs , should_delete_workspace , access_token
1353+ )
13021354 message = "All workflows named '{}' have been deleted." .format (
1303- workflow . split ( "." )[ 0 ]
1355+ workflow_base
13041356 )
1305- else :
1306- message = get_workflow_status_change_msg (workflow , "deleted" )
1307- display_message (message , msg_type = "success" )
1357+ display_message (message , msg_type = "success" )
1358+ return
1359+
1360+ # Otherwise, detect whether this run has restarts
1361+ wf_status = get_workflow_status (workflow , access_token ) or {}
1362+ full_name = wf_status .get ("name" ) or workflow
1363+ wf_id = wf_status .get ("id" ) # may be None
1364+
1365+ # If already deleted, do not delete again
1366+ if wf_status .get ("status" ) == "deleted" :
1367+ display_message (
1368+ f"Workflow run '{ full_name } ' is already deleted." ,
1369+ msg_type = "info" ,
1370+ )
1371+ return
1372+
1373+ base_name , _ , _ = parse_workflow_run_number (full_name )
1374+ major_key = get_run_number_major_key (full_name )
1375+ related_runs = []
1376+ if base_name and major_key :
1377+ # List non-deleted runs of this workflow name, keep only same workspace group/number
1378+ status_filter = RUN_STATUSES .copy ()
1379+ if "deleted" in status_filter :
1380+ status_filter .remove ("deleted" )
1381+ runs = get_workflows (
1382+ access_token = access_token ,
1383+ type = "batch" ,
1384+ page = 1 ,
1385+ size = CLI_WORKFLOWS_LIST_MAX_RESULTS ,
1386+ status = status_filter ,
1387+ workflow = base_name ,
1388+ )
1389+ related_runs = [
1390+ r
1391+ for r in (runs or [])
1392+ if get_run_number_major_key (r .get ("name" )) == major_key
1393+ ]
1394+
1395+ has_restart_series = len (related_runs ) > 1
1396+ if has_restart_series and not include_all_restarts :
1397+ labels = [format_run_number_label (r .get ("name" )) for r in related_runs ]
1398+ display_message (
1399+ "Cannot delete workflow run '{}': it is part of a restart series. "
1400+ "Restarted runs share the same workspace, so deleting one run would remove the "
1401+ "shared workspace and leave other runs in an inconsistent state.\n "
1402+ "Related runs: {}\n "
1403+ "Rerun the command with --include-all-restarts to delete this run and its restarts." .format (
1404+ full_name , format_run_label_list (labels , MAX_RUN_LABELS_SHOWN )
1405+ ),
1406+ msg_type = "error" ,
1407+ )
1408+ sys .exit (1 )
1409+
1410+ if has_restart_series and include_all_restarts :
1411+ # Delete workspace once, mark related runs deleted without deleting workspace again
1412+ workflow_id_or_name = wf_id or full_name
1413+ delete_workflow (workflow_id_or_name , False , True , access_token )
1414+ primary_id = wf_id
1415+ primary_name = full_name
1416+ for r in related_runs :
1417+ rid = r .get ("id" )
1418+ rname = r .get ("name" )
1419+ if (primary_id and rid == primary_id ) or (rname == primary_name ):
1420+ continue
1421+ delete_workflow (rid or rname , False , False , access_token )
1422+
1423+ labels = [format_run_number_label (r .get ("name" )) for r in related_runs ]
1424+ display_message (
1425+ "Workflow run '{}' including its restarts have been deleted ({})." .format (
1426+ full_name , format_run_label_list (labels , MAX_RUN_LABELS_SHOWN )
1427+ ),
1428+ msg_type = "success" ,
1429+ )
1430+ return
1431+
1432+ # Normal single workflow run delete (no restarts)
1433+ delete_workflow (full_name , False , should_delete_workspace , access_token )
1434+ display_message (
1435+ get_workflow_status_change_msg (full_name , "deleted" ), msg_type = "success"
1436+ )
13081437
13091438 except Exception as e :
13101439 logging .debug (traceback .format_exc ())
0 commit comments