use k8s_openapi::api::core::v1::{ConfigMap, Namespace, Pod, Secret, Service};
use kube::{
api::{Api, ListParams, PostParams},
Client, Config,
};
use std::collections::{BTreeMap, HashMap};
use crate::error::{Error, Result};
use fusabi_host::Value;
pub struct K8sClient {
client: Client,
namespace: String,
}
impl K8sClient {
pub async fn new_in_cluster() -> Result<Self> {
let config = Config::incluster()
.map_err(|e| Error::K8s(format!("in-cluster config failed: {}", e)))?;
let client = Client::try_from(config)
.map_err(|e| Error::K8s(format!("client creation failed: {}", e)))?;
Ok(Self {
client,
namespace: "default".to_string(),
})
}
pub async fn from_kubeconfig() -> Result<Self> {
let config = Config::infer()
.await
.map_err(|e| Error::K8s(format!("kubeconfig inference failed: {}", e)))?;
let client = Client::try_from(config)
.map_err(|e| Error::K8s(format!("client creation failed: {}", e)))?;
Ok(Self {
client,
namespace: "default".to_string(),
})
}
pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
self.namespace = namespace.into();
self
}
pub fn namespace(&self) -> &str {
&self.namespace
}
pub async fn list_pods(&self, label_selector: Option<&str>) -> Result<Vec<PodInfo>> {
let api: Api<Pod> = Api::namespaced(self.client.clone(), &self.namespace);
let mut lp = ListParams::default();
if let Some(selector) = label_selector {
lp = lp.labels(selector);
}
let pods = api
.list(&lp)
.await
.map_err(|e| Error::K8s(format!("list pods failed: {}", e)))?;
Ok(pods
.items
.into_iter()
.map(|pod| PodInfo::from_pod(&pod))
.collect())
}
pub async fn get_configmap(&self, name: &str) -> Result<HashMap<String, String>> {
let api: Api<ConfigMap> = Api::namespaced(self.client.clone(), &self.namespace);
let cm = api
.get(name)
.await
.map_err(|e| Error::K8s(format!("get configmap failed: {}", e)))?;
let btree = cm.data.unwrap_or_default();
Ok(btree.into_iter().collect())
}
pub async fn get_secret(&self, name: &str) -> Result<HashMap<String, String>> {
let api: Api<Secret> = Api::namespaced(self.client.clone(), &self.namespace);
let secret = api
.get(name)
.await
.map_err(|e| Error::K8s(format!("get secret failed: {}", e)))?;
let mut result = HashMap::new();
if let Some(data) = secret.data {
for (key, value) in data {
if let Ok(decoded) = String::from_utf8(value.0) {
result.insert(key, decoded);
}
}
}
Ok(result)
}
pub async fn list_namespaces(&self) -> Result<Vec<String>> {
let api: Api<Namespace> = Api::all(self.client.clone());
let namespaces = api
.list(&ListParams::default())
.await
.map_err(|e| Error::K8s(format!("list namespaces failed: {}", e)))?;
Ok(namespaces
.items
.into_iter()
.filter_map(|ns| ns.metadata.name)
.collect())
}
}
#[derive(Debug, Clone)]
pub struct PodInfo {
pub name: String,
pub namespace: String,
pub phase: String,
pub pod_ip: Option<String>,
pub node_name: Option<String>,
pub labels: HashMap<String, String>,
}
impl PodInfo {
fn from_pod(pod: &Pod) -> Self {
let metadata = &pod.metadata;
let status = pod.status.as_ref();
Self {
name: metadata.name.clone().unwrap_or_default(),
namespace: metadata.namespace.clone().unwrap_or_default(),
phase: status
.and_then(|s| s.phase.clone())
.unwrap_or_else(|| "Unknown".to_string()),
pod_ip: status.and_then(|s| s.pod_ip.clone()),
node_name: pod.spec.as_ref().and_then(|s| s.node_name.clone()),
labels: metadata.labels.clone().unwrap_or_default().into_iter().collect(),
}
}
pub fn to_value(&self) -> Value {
let mut map = HashMap::new();
map.insert("name".to_string(), Value::String(self.name.clone()));
map.insert("namespace".to_string(), Value::String(self.namespace.clone()));
map.insert("phase".to_string(), Value::String(self.phase.clone()));
if let Some(ref ip) = self.pod_ip {
map.insert("pod_ip".to_string(), Value::String(ip.clone()));
}
if let Some(ref node) = self.node_name {
map.insert("node_name".to_string(), Value::String(node.clone()));
}
let labels: HashMap<String, Value> = self
.labels
.iter()
.map(|(k, v)| (k.clone(), Value::String(v.clone())))
.collect();
map.insert("labels".to_string(), Value::Map(labels));
Value::Map(map)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pod_info_to_value() {
let info = PodInfo {
name: "test-pod".to_string(),
namespace: "default".to_string(),
phase: "Running".to_string(),
pod_ip: Some("10.0.0.1".to_string()),
node_name: Some("node-1".to_string()),
labels: HashMap::from([("app".to_string(), "test".to_string())]),
};
let value = info.to_value();
if let Value::Map(map) = value {
assert_eq!(map.get("name"), Some(&Value::String("test-pod".to_string())));
assert_eq!(map.get("phase"), Some(&Value::String("Running".to_string())));
} else {
panic!("Expected Map value");
}
}
}