From ebc82dbe58eef19ca1e049f0b2099b702fe3725e Mon Sep 17 00:00:00 2001 From: hugocasa Date: Mon, 15 Dec 2025 19:03:28 +0100 Subject: [PATCH] feat(backend): stop schedules and cancel jobs when archiving a workspace (#7377) --- ...f677925474aad885bf1c0780bdb978225517e.json | 22 ++++++ ...1c536ebea8a76b75cda188ec91fcc0fc274a7.json | 22 ++++++ backend/windmill-api/src/workspaces.rs | 69 ++++++++++++++++++- 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 backend/.sqlx/query-03669873e4e3b22c737d5170821f677925474aad885bf1c0780bdb978225517e.json create mode 100644 backend/.sqlx/query-b9c520fdc29b17977bf1c0b30311c536ebea8a76b75cda188ec91fcc0fc274a7.json diff --git a/backend/.sqlx/query-03669873e4e3b22c737d5170821f677925474aad885bf1c0780bdb978225517e.json b/backend/.sqlx/query-03669873e4e3b22c737d5170821f677925474aad885bf1c0780bdb978225517e.json new file mode 100644 index 0000000000..89f7c407a0 --- /dev/null +++ b/backend/.sqlx/query-03669873e4e3b22c737d5170821f677925474aad885bf1c0780bdb978225517e.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE schedule SET enabled = false WHERE workspace_id = $1 AND enabled = true RETURNING path", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "03669873e4e3b22c737d5170821f677925474aad885bf1c0780bdb978225517e" +} diff --git a/backend/.sqlx/query-b9c520fdc29b17977bf1c0b30311c536ebea8a76b75cda188ec91fcc0fc274a7.json b/backend/.sqlx/query-b9c520fdc29b17977bf1c0b30311c536ebea8a76b75cda188ec91fcc0fc274a7.json new file mode 100644 index 0000000000..dbd81df0b1 --- /dev/null +++ b/backend/.sqlx/query-b9c520fdc29b17977bf1c0b30311c536ebea8a76b75cda188ec91fcc0fc274a7.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id FROM v2_job_queue WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "b9c520fdc29b17977bf1c0b30311c536ebea8a76b75cda188ec91fcc0fc274a7" +} diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index eec1efb6d2..b231c4a447 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -3327,11 +3327,73 @@ async fn archive_workspace( authed: ApiAuthed, ) -> Result { require_admin(authed.is_admin, &authed.username)?; + + // Step 1: Disable all schedules and clear their queued jobs + let mut tx = db.begin().await?; + let disabled_schedules = sqlx::query_scalar!( + "UPDATE schedule SET enabled = false WHERE workspace_id = $1 AND enabled = true RETURNING path", + &w_id + ) + .fetch_all(&mut *tx) + .await?; + + let schedules_count = disabled_schedules.len(); + tracing::info!( + "Disabled {} schedules in workspace {}", + schedules_count, + w_id + ); + + // Clear all schedule-related jobs using the existing clear_schedule function + for schedule_path in &disabled_schedules { + crate::schedule::clear_schedule(&mut tx, schedule_path, &w_id).await?; + } + + tx.commit().await?; + + // Step 2: Get all remaining queued jobs for this workspace (non-schedule jobs) + let jobs_to_cancel = + sqlx::query_scalar!("SELECT id FROM v2_job_queue WHERE workspace_id = $1", &w_id) + .fetch_all(&db) + .await?; + + let jobs_count = jobs_to_cancel.len(); + tracing::info!( + "Found {} remaining jobs to cancel in workspace {}", + jobs_count, + w_id + ); + + // Step 3: Cancel all remaining jobs using the existing cancel_jobs function + let canceled_count = if !jobs_to_cancel.is_empty() { + let axum::Json(canceled_jobs) = crate::jobs::cancel_jobs( + jobs_to_cancel, + &db, + &authed.username, + &w_id, + false, // force_cancel + ) + .await?; + + let count = canceled_jobs.len(); + tracing::info!("Canceled {} jobs in workspace {}", count, w_id); + count + } else { + 0 + }; + + // Step 4: Archive the workspace let mut tx = db.begin().await?; sqlx::query!("UPDATE workspace SET deleted = true WHERE id = $1", &w_id) .execute(&mut *tx) .await?; + let mut audit_params = HashMap::new(); + audit_params.insert("disabled_schedules", schedules_count.to_string()); + audit_params.insert("canceled_jobs", canceled_count.to_string()); + let audit_params_refs: HashMap<&str, &str> = + audit_params.iter().map(|(k, v)| (*k, v.as_str())).collect(); + audit_log( &mut *tx, &authed, @@ -3339,12 +3401,15 @@ async fn archive_workspace( ActionKind::Update, &w_id, Some(&authed.email), - None, + Some(audit_params_refs), ) .await?; tx.commit().await?; - Ok(format!("Archived workspace {}", &w_id)) + Ok(format!( + "Archived workspace {}, disabled {} schedules and canceled {} jobs", + &w_id, schedules_count, canceled_count + )) } async fn leave_workspace(