use std::collections::HashMap;
use async_recursion::async_recursion;
use kube::Resource;
use kube::api::ListParams;
use kube::discovery::{
ApiCapabilities,
Scope,
};
use tracing::*;
use super::*;
use crate::k8s::{
DynamicApiSet,
format_gvk_name,
};
use crate::prelude::*;
pub struct OwnersCache {
apiset: DynamicApiSet,
owners: HashMap<(GVK, String), Vec<metav1::OwnerReference>>,
}
impl OwnersCache {
pub fn new(apiset: DynamicApiSet) -> OwnersCache {
OwnersCache { apiset, owners: HashMap::new() }
}
pub fn new_from_parts(
apiset: DynamicApiSet,
owners: HashMap<(GVK, String), Vec<metav1::OwnerReference>>,
) -> OwnersCache {
OwnersCache { apiset, owners }
}
#[async_recursion]
pub async fn compute_owners_for(&mut self, gvk: &GVK, obj: &(impl Resource + Sync)) -> Vec<metav1::OwnerReference> {
let ns_name = obj.namespaced_name();
let gvk_name = format_gvk_name(gvk, &ns_name);
debug!("computing owner references for {gvk_name}");
let key = (gvk.clone(), ns_name.clone());
if let Some(owners) = self.owners.get(&key) {
debug!("found owners {owners:?} for {gvk_name} in cache");
return owners.clone();
}
let mut owners = Vec::new();
for rf in obj.owner_references() {
let owner_gvk = match GVK::from_owner_ref(rf) {
Ok(gvk) => gvk,
Err(err) => {
error!("malformed owner reference {rf:?}: {err}");
continue;
},
};
let (api, cap) = match self.apiset.unnamespaced_api_by_gvk(&owner_gvk).await {
Ok((a, c)) => (a, c),
Err(err) => {
warn!("could not query {owner_gvk}: {err}; skipping ownerref");
continue;
},
};
let sel = build_owner_selector(&rf.name, obj, cap);
let items = match api.list(&sel).await {
Ok(objlist) => objlist.items,
Err(err) => {
error!("Could not list {owner_gvk}: {err}; skipping ownerref");
continue;
},
};
if items.len() != 1 {
error!("could not find single owner for {gvk_name}, found {items:?}; skipping ownerref");
continue;
}
owners.push(rf.clone());
owners.extend(self.compute_owners_for(&owner_gvk, &items[0]).await);
}
debug!("computed owners {owners:?} for {gvk_name}");
self.owners.insert(key, owners.clone());
owners
}
pub async fn lookup_by_name_or_obj(
&mut self,
gvk: &GVK,
ns_name: &str,
maybe_obj: Option<&(impl Resource + Sync)>,
) -> Vec<metav1::OwnerReference> {
match (self.owners.get(&(gvk.clone(), ns_name.into())), maybe_obj) {
(Some(o), _) => o.clone(),
(None, Some(obj)) => self.compute_owners_for(gvk, obj).await,
_ => {
error!("could not determine owner chain for {ns_name}");
vec![]
},
}
}
}
fn build_owner_selector(owner_name: &str, obj: &(impl Resource + Sync), owner_cap: ApiCapabilities) -> ListParams {
let sel = match owner_cap.scope {
Scope::Cluster => Some(format!("metadata.name={owner_name}")),
Scope::Namespaced => {
Some(format!("metadata.namespace={},metadata.name={}", obj.namespace().unwrap(), owner_name))
},
};
ListParams { field_selector: sel, ..Default::default() }
}