k8s_operator_controller/
finalizer.rs

1use kube::api::{Patch, PatchParams, Resource};
2use kube::{Api, Client};
3use serde::de::DeserializeOwned;
4use serde::Serialize;
5use serde_json::json;
6use tracing::info;
7
8use k8s_operator_core::OperatorError;
9
10pub const DEFAULT_FINALIZER: &str = "k8s-operator.io/finalizer";
11
12pub async fn add_finalizer<K>(client: &Client, resource: &K, finalizer: &str) -> k8s_operator_core::Result<()>
13where
14    K: Resource + Clone + std::fmt::Debug + Serialize + DeserializeOwned + Send + Sync + 'static,
15    K::DynamicType: Default,
16{
17    let name = resource.meta().name.clone().ok_or_else(|| {
18        OperatorError::Internal("Resource has no name".to_string())
19    })?;
20
21    let api: Api<K> = Api::all(client.clone());
22
23    let patch = json!({
24        "metadata": {
25            "finalizers": [finalizer]
26        }
27    });
28
29    api.patch(&name, &PatchParams::apply("k8s-operator"), &Patch::Merge(&patch))
30        .await
31        .map_err(|e| OperatorError::KubeError(e))?;
32
33    info!("Added finalizer {} to {}", finalizer, name);
34    Ok(())
35}
36
37pub async fn remove_finalizer<K>(client: &Client, resource: &K, finalizer: &str) -> k8s_operator_core::Result<()>
38where
39    K: Resource + Clone + std::fmt::Debug + Serialize + DeserializeOwned + Send + Sync + 'static,
40    K::DynamicType: Default,
41{
42    let name = resource.meta().name.clone().ok_or_else(|| {
43        OperatorError::Internal("Resource has no name".to_string())
44    })?;
45
46    let api: Api<K> = Api::all(client.clone());
47
48    let current_finalizers: Vec<String> = resource
49        .meta()
50        .finalizers
51        .clone()
52        .unwrap_or_default()
53        .into_iter()
54        .filter(|f| f != finalizer)
55        .collect();
56
57    let patch = json!({
58        "metadata": {
59            "finalizers": current_finalizers
60        }
61    });
62
63    api.patch(&name, &PatchParams::apply("k8s-operator"), &Patch::Merge(&patch))
64        .await
65        .map_err(|e| OperatorError::KubeError(e))?;
66
67    info!("Removed finalizer {} from {}", finalizer, name);
68    Ok(())
69}
70
71pub fn has_finalizer<K>(resource: &K, finalizer: &str) -> bool
72where
73    K: Resource,
74{
75    resource
76        .meta()
77        .finalizers
78        .as_ref()
79        .map(|f| f.contains(&finalizer.to_string()))
80        .unwrap_or(false)
81}
82
83pub fn is_being_deleted<K>(resource: &K) -> bool
84where
85    K: Resource,
86{
87    resource.meta().deletion_timestamp.is_some()
88}
89
90pub struct FinalizerGuard<'a, K>
91where
92    K: Resource + Clone + std::fmt::Debug + Serialize + DeserializeOwned + Send + Sync + 'static,
93    K::DynamicType: Default,
94{
95    client: &'a Client,
96    resource: &'a K,
97    finalizer: String,
98}
99
100impl<'a, K> FinalizerGuard<'a, K>
101where
102    K: Resource + Clone + std::fmt::Debug + Serialize + DeserializeOwned + Send + Sync + 'static,
103    K::DynamicType: Default,
104{
105    pub fn new(client: &'a Client, resource: &'a K) -> Self {
106        Self {
107            client,
108            resource,
109            finalizer: DEFAULT_FINALIZER.to_string(),
110        }
111    }
112
113    pub fn with_finalizer(mut self, finalizer: impl Into<String>) -> Self {
114        self.finalizer = finalizer.into();
115        self
116    }
117
118    pub async fn ensure(&self) -> k8s_operator_core::Result<()> {
119        if !has_finalizer(self.resource, &self.finalizer) {
120            add_finalizer(self.client, self.resource, &self.finalizer).await?;
121        }
122        Ok(())
123    }
124
125    pub fn is_being_deleted(&self) -> bool {
126        is_being_deleted(self.resource)
127    }
128
129    pub fn has_finalizer(&self) -> bool {
130        has_finalizer(self.resource, &self.finalizer)
131    }
132
133    pub async fn cleanup_complete(&self) -> k8s_operator_core::Result<()> {
134        if has_finalizer(self.resource, &self.finalizer) {
135            remove_finalizer(self.client, self.resource, &self.finalizer).await?;
136        }
137        Ok(())
138    }
139}