From f0411ef90391dec6edb5bc475402929a0d1f995b Mon Sep 17 00:00:00 2001 From: Roy Kaufman Date: Wed, 1 Jul 2026 12:59:39 +0300 Subject: [PATCH] rvs: Fix race of ApprovedImage adoption with TEC object deletion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The race scenario: - Assume the operator is installed and TEC object exists - Apply an approved-image yaml file (create a new CR instance) - Delete the TEC object (immediately) - The new approved-image is not deleted with the TEC object This happens because kube-rs finalizer() does not call the Apply handler on the first reconcile — it only adds the finalizer and returns. This means adopt_approved_image inside image_add_reconcile never runs on the first reconcile. If the TEC is deleted before the second reconcile, the owner reference is never set and GC (garbage collector) cannot cascade-delete the ApprovedImage. Move adoption before the finalizer() call in image_reconcile so it runs on the very first reconcile. For more information see https://docs.rs/kube/latest/kube/runtime/finalizer/fn.finalizer.html in sections Guarantees, Assumptions (Third point) and Expected Flow (from 1 to 6). Signed-off-by: Roy Kaufman --- operator/src/reference_values.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/operator/src/reference_values.rs b/operator/src/reference_values.rs index f202c533..4077d9bb 100644 --- a/operator/src/reference_values.rs +++ b/operator/src/reference_values.rs @@ -247,6 +247,23 @@ async fn image_reconcile( .await .map_err(|e| -> ControllerError { e.into() })?; + let uid_owns = |uid: &String| { + let refs = image.metadata.owner_references.as_ref(); + refs.map(|os| os.iter().any(|o| o.uid == *uid)) + }; + let cluster_owns = |cluster: &TrustedExecutionCluster| { + let uid = cluster.metadata.uid.as_ref(); + uid.and_then(uid_owns).unwrap_or(false) + }; + + if let Some(ref cluster) = cluster + && !cluster_owns(cluster) + { + adopt_approved_image(kube_client.clone(), &name, cluster) + .await + .map_err(|e| -> ControllerError { e.into() })?; + } + let images: Api = Api::default_namespaced(kube_client.clone()); finalizer(&images, APPROVED_IMAGE_FINALIZER, image, |ev| async { match ev { @@ -277,19 +294,6 @@ async fn image_add_reconcile( info!("TrustedExecutionCluster is being deleted, deferring image processing for {name}"); return Ok(Action::requeue(Duration::from_secs(5))); } - let uid_owns = |uid: &String| { - let refs = image.metadata.owner_references.as_ref(); - refs.map(|os| os.iter().any(|o| o.uid == *uid)) - }; - let cluster_owns = |cluster: &TrustedExecutionCluster| { - let uid = cluster.metadata.uid.as_ref(); - uid.and_then(uid_owns).unwrap_or(false) - }; - // Adopt the image by adding TEC as owner reference if not already owned - if !cluster_owns(&cluster) { - adopt_approved_image(client.clone(), name, &cluster).await?; - } - let (action, reason) = match handle_new_image(client.clone(), image).await { Ok(reason) => (Action::await_change(), reason), Err(e) => {