blackjack/
namespace.rs

1// Copyright 2024 Ole Kliemann
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::error::{Error, Result};
5use k8s_openapi::api::core::v1::Namespace;
6use kube::api::{DeleteParams, Patch, PatchParams, PostParams};
7use kube::{Api, Client};
8use serde_json::json;
9use tokio::time::{sleep, Duration};
10
11pub struct NamespaceHandle {
12    namespace: String,
13    api: Api<Namespace>,
14}
15
16impl NamespaceHandle {
17    pub fn new(client: Client, namespace: &str) -> Self {
18        let api: Api<Namespace> = Api::all(client.clone());
19        NamespaceHandle {
20            namespace: namespace.to_string(),
21            api,
22        }
23    }
24
25    pub async fn create(&self) -> Result<()> {
26        let ns = Namespace {
27            metadata: kube::api::ObjectMeta {
28                name: Some(self.namespace.clone()),
29                ..Default::default()
30            },
31            ..Default::default()
32        };
33
34        match self.api.create(&PostParams::default(), &ns).await {
35            Ok(_) => Ok(()),
36            Err(kube::Error::Api(ae)) if ae.code == 409 => Err(Error::NamespaceExists),
37            Err(e) => Err(Error::from(e)),
38        }
39    }
40
41    pub async fn delete(&self) -> Result<()> {
42        log::debug!("Deleting namespace");
43        if self.try_delete().await? {
44            self.force_delete().await?;
45        } else {
46            log::debug!("Namespace '{}' deleted gracefully.", self.namespace);
47        }
48        Ok(())
49    }
50
51    async fn try_delete(&self) -> Result<bool> {
52        let delete_params = DeleteParams::default();
53
54        match self.api.delete(&self.namespace, &delete_params).await {
55            Ok(delete_response) => {
56                if delete_response.left().is_some() {
57                    if self.wait_for_deletion(30).await? {
58                        Ok(false)
59                    } else {
60                        Ok(true)
61                    }
62                } else {
63                    Ok(false)
64                }
65            }
66            Err(kube::Error::Api(ae)) if ae.code == 404 => Ok(false),
67            Err(e) => Err(Error::from(e)),
68        }
69    }
70
71    async fn wait_for_deletion(&self, timeout_seconds: u64) -> Result<bool> {
72        log::debug!("Waiting for namespace deletion");
73        for _ in 0..timeout_seconds {
74            match self.api.get(&self.namespace).await {
75                Ok(_) => sleep(Duration::from_secs(1)).await,
76                Err(kube::Error::Api(ae)) if ae.code == 404 => {
77                    return Ok(true);
78                }
79                Err(e) => return Err(Error::from(e)),
80            }
81        }
82        Ok(false)
83    }
84
85    async fn force_delete(&self) -> Result<()> {
86        log::debug!("Force deleting namespace");
87        let patch = json!({
88            "metadata": {
89                "finalizers": null
90            }
91        });
92
93        self.api
94            .patch(
95                &self.namespace,
96                &PatchParams::default(),
97                &Patch::Merge(&patch),
98            )
99            .await
100            .map_err(Error::from)?;
101
102        let delete_params = DeleteParams {
103            grace_period_seconds: Some(0),
104            ..DeleteParams::default()
105        };
106
107        match self.api.delete(&self.namespace, &delete_params).await {
108            Ok(_) => {
109                self.wait_for_deletion(10).await?;
110                log::debug!("Namespace '{}' force deleted.", self.namespace);
111                Ok(())
112            }
113            Err(e) => Err(Error::from(e)),
114        }
115    }
116}