lmrc_kubernetes/configmap/
manager.rs

1//! ConfigMap manager implementation.
2
3use crate::configmap::ConfigMapSpec;
4use crate::error::{Error, Result};
5use k8s_openapi::ByteString;
6use k8s_openapi::api::core::v1::ConfigMap;
7use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
8use kube::Client as KubeClient;
9use kube::api::{Api, DeleteParams, ListParams, Patch, PatchParams};
10use std::collections::BTreeMap;
11
12/// Manager for Kubernetes ConfigMap operations.
13///
14/// This manager provides methods to create, update, delete, and retrieve ConfigMaps
15/// in a Kubernetes cluster.
16pub struct ConfigMapManager {
17    api: Api<ConfigMap>,
18    namespace: String,
19}
20
21impl ConfigMapManager {
22    /// Creates a new ConfigMapManager.
23    ///
24    /// # Arguments
25    ///
26    /// * `client` - The Kubernetes client
27    /// * `namespace` - The namespace to operate in
28    pub fn new(client: KubeClient, namespace: impl Into<String>) -> Self {
29        let namespace = namespace.into();
30        let api = Api::namespaced(client, &namespace);
31        Self { api, namespace }
32    }
33
34    /// Creates or updates a ConfigMap using server-side apply.
35    ///
36    /// # Arguments
37    ///
38    /// * `spec` - The ConfigMap specification
39    ///
40    /// # Returns
41    ///
42    /// Returns the created/updated ConfigMap name on success.
43    ///
44    /// # Examples
45    ///
46    /// ```no_run
47    /// use lmrc_kubernetes::{Client, ClientConfig, ConfigMapSpec};
48    ///
49    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
50    /// let config = ClientConfig::infer().await?;
51    /// let client = Client::new(config, "default").await?;
52    ///
53    /// let configmap = ConfigMapSpec::new("app-config")
54    ///     .with_data("database.url", "postgres://localhost:5432/mydb");
55    ///
56    /// client.configmaps().apply(&configmap).await?;
57    /// # Ok(())
58    /// # }
59    /// ```
60    pub async fn apply(&self, spec: &ConfigMapSpec) -> Result<String> {
61        // Validate spec
62        spec.validate().map_err(Error::ValidationError)?;
63
64        let configmap = self.spec_to_k8s(spec)?;
65        let name = spec.name();
66
67        let params = PatchParams::apply("kubernetes-manager").force();
68        let patch = Patch::Apply(&configmap);
69
70        self.api.patch(name, &params, &patch).await.map_err(|e| {
71            Error::ConfigMapError(format!("Failed to apply ConfigMap '{}': {}", name, e))
72        })?;
73
74        Ok(name.to_string())
75    }
76
77    /// Gets a ConfigMap by name.
78    ///
79    /// # Arguments
80    ///
81    /// * `name` - The ConfigMap name
82    ///
83    /// # Returns
84    ///
85    /// Returns the ConfigMap if found.
86    ///
87    /// # Examples
88    ///
89    /// ```no_run
90    /// use lmrc_kubernetes::{Client, ClientConfig};
91    ///
92    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
93    /// let config = ClientConfig::infer().await?;
94    /// let client = Client::new(config, "default").await?;
95    ///
96    /// let configmap = client.configmaps().get("app-config").await?;
97    /// println!("ConfigMap: {:?}", configmap.metadata.name);
98    /// # Ok(())
99    /// # }
100    /// ```
101    pub async fn get(&self, name: &str) -> Result<ConfigMap> {
102        self.api
103            .get(name)
104            .await
105            .map_err(|_| Error::ResourceNotFound {
106                kind: "ConfigMap".to_string(),
107                name: name.to_string(),
108                namespace: self.namespace.clone(),
109            })
110    }
111
112    /// Lists all ConfigMaps in the namespace.
113    ///
114    /// # Returns
115    ///
116    /// Returns a vector of ConfigMaps.
117    ///
118    /// # Examples
119    ///
120    /// ```no_run
121    /// use lmrc_kubernetes::{Client, ClientConfig};
122    ///
123    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
124    /// let config = ClientConfig::infer().await?;
125    /// let client = Client::new(config, "default").await?;
126    ///
127    /// let configmaps = client.configmaps().list().await?;
128    /// println!("Found {} ConfigMaps", configmaps.len());
129    /// # Ok(())
130    /// # }
131    /// ```
132    pub async fn list(&self) -> Result<Vec<ConfigMap>> {
133        let lp = ListParams::default();
134        let list = self
135            .api
136            .list(&lp)
137            .await
138            .map_err(|e| Error::ConfigMapError(format!("Failed to list ConfigMaps: {}", e)))?;
139
140        Ok(list.items)
141    }
142
143    /// Lists ConfigMaps matching the given label selector.
144    ///
145    /// # Arguments
146    ///
147    /// * `label_selector` - The label selector (e.g., "app=myapp,env=prod")
148    ///
149    /// # Returns
150    ///
151    /// Returns a vector of matching ConfigMaps.
152    ///
153    /// # Examples
154    ///
155    /// ```no_run
156    /// use lmrc_kubernetes::{Client, ClientConfig};
157    ///
158    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
159    /// let config = ClientConfig::infer().await?;
160    /// let client = Client::new(config, "default").await?;
161    ///
162    /// let configmaps = client.configmaps()
163    ///     .list_by_label("app=myapp")
164    ///     .await?;
165    /// # Ok(())
166    /// # }
167    /// ```
168    pub async fn list_by_label(&self, label_selector: &str) -> Result<Vec<ConfigMap>> {
169        let lp = ListParams::default().labels(label_selector);
170        let list = self
171            .api
172            .list(&lp)
173            .await
174            .map_err(|e| Error::ConfigMapError(format!("Failed to list ConfigMaps: {}", e)))?;
175
176        Ok(list.items)
177    }
178
179    /// Deletes a ConfigMap by name.
180    ///
181    /// # Arguments
182    ///
183    /// * `name` - The ConfigMap name
184    ///
185    /// # Examples
186    ///
187    /// ```no_run
188    /// use lmrc_kubernetes::{Client, ClientConfig};
189    ///
190    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
191    /// let config = ClientConfig::infer().await?;
192    /// let client = Client::new(config, "default").await?;
193    ///
194    /// client.configmaps().delete("app-config").await?;
195    /// # Ok(())
196    /// # }
197    /// ```
198    pub async fn delete(&self, name: &str) -> Result<()> {
199        let dp = DeleteParams::default();
200        self.api.delete(name, &dp).await.map_err(|e| {
201            Error::ConfigMapError(format!("Failed to delete ConfigMap '{}': {}", name, e))
202        })?;
203
204        Ok(())
205    }
206
207    /// Checks if a ConfigMap exists.
208    ///
209    /// # Arguments
210    ///
211    /// * `name` - The ConfigMap name
212    ///
213    /// # Returns
214    ///
215    /// Returns `true` if the ConfigMap exists, `false` otherwise.
216    ///
217    /// # Examples
218    ///
219    /// ```no_run
220    /// use lmrc_kubernetes::{Client, ClientConfig};
221    ///
222    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
223    /// let config = ClientConfig::infer().await?;
224    /// let client = Client::new(config, "default").await?;
225    ///
226    /// if client.configmaps().exists("app-config").await? {
227    ///     println!("ConfigMap exists");
228    /// }
229    /// # Ok(())
230    /// # }
231    /// ```
232    pub async fn exists(&self, name: &str) -> Result<bool> {
233        match self.get(name).await {
234            Ok(_) => Ok(true),
235            Err(Error::ResourceNotFound { .. }) => Ok(false),
236            Err(e) => Err(e),
237        }
238    }
239
240    /// Converts ConfigMapSpec to Kubernetes API ConfigMap.
241    fn spec_to_k8s(&self, spec: &ConfigMapSpec) -> Result<ConfigMap> {
242        let mut labels = BTreeMap::new();
243        for (k, v) in spec.labels() {
244            labels.insert(k.clone(), v.clone());
245        }
246
247        let mut data = BTreeMap::new();
248        for (k, v) in spec.data() {
249            data.insert(k.clone(), v.clone());
250        }
251
252        let mut binary_data = BTreeMap::new();
253        for (k, v) in spec.binary_data() {
254            binary_data.insert(k.clone(), ByteString(v.clone()));
255        }
256
257        Ok(ConfigMap {
258            metadata: ObjectMeta {
259                name: Some(spec.name().to_string()),
260                namespace: Some(self.namespace.clone()),
261                labels: if labels.is_empty() {
262                    None
263                } else {
264                    Some(labels)
265                },
266                ..Default::default()
267            },
268            data: if data.is_empty() { None } else { Some(data) },
269            binary_data: if binary_data.is_empty() {
270                None
271            } else {
272                Some(binary_data)
273            },
274            immutable: if spec.is_immutable() {
275                Some(true)
276            } else {
277                None
278            },
279        })
280    }
281}