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, ¶ms, &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}