use kube::api::{DeleteParams, ListParams, Patch, PatchParams};
use kube::{Api, Client, ResourceExt};
use serde_json::json;
use tracing::info;
use crate::error::Result;
use crate::scope::ApiScope;
use crate::traits::KubeResource;
async fn gc_inner<T, F>(
list_api: Api<T>,
make_api: impl Fn(&str) -> Api<T>,
label_selector: &str,
is_desired: F,
) -> Result<()>
where
T: KubeResource,
F: Fn(&T) -> bool,
{
let existing = list_api
.list(&ListParams::default().labels(label_selector))
.await?;
for resource in existing {
let name = resource.name_any();
let ns = resource.namespace().unwrap_or_default();
let api = make_api(&ns);
if is_desired(&resource) {
continue;
}
if resource.meta().deletion_timestamp.is_some() {
info!(%name, %ns, "Resource is terminating — clearing finalizers");
clear_finalizers(&api, &name).await;
continue;
}
info!(%name, %ns, "Deleting orphaned resource");
match api.delete(&name, &DeleteParams::foreground()).await {
Ok(_) => clear_finalizers(&api, &name).await,
Err(kube::Error::Api(e)) if e.code == 404 => {
info!(%name, "Already deleted, skipping");
}
Err(e) => return Err(e.into()),
}
}
Ok(())
}
async fn clear_finalizers<T>(api: &Api<T>, name: &str)
where
T: KubeResource,
{
let patch = json!({ "metadata": { "finalizers": null } });
let _ = api
.patch(name, &PatchParams::default(), &Patch::Merge(&patch))
.await;
}
pub async fn gc_resources<T, Scope>(
client: Client,
scope: Scope,
label_selector: &str,
is_desired: impl Fn(&T) -> bool,
) -> Result<()>
where
T: KubeResource,
Scope: ApiScope<T>,
{
let kind = T::kind(&());
info!(%kind, %label_selector, "Starting GC");
let list_api: Api<T> = Api::all(client.clone());
let scoped_api: Api<T> = scope.into_api(client);
gc_inner(
list_api,
|_ns| scoped_api.clone(),
label_selector,
is_desired,
)
.await
}