use anyhow::Result;
use kube::{
api::{ObjectMeta, PostParams},
Api, Error, Resource,
};
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
pub enum Outcome<T> {
Created(T),
Updated(T),
Unchanged(T),
}
impl<T> Outcome<T> {
pub fn resource(self) -> T {
match self {
Self::Created(r) => r,
Self::Updated(r) => r,
Self::Unchanged(r) => r,
}
}
}
pub async fn create_or_update_by<T, S1, S2, C, F, E, Eq>(
api: &Api<T>,
namespace: Option<S1>,
name: S2,
creator: C,
eq: Eq,
mutator: F,
) -> Result<Outcome<T>, E>
where
T: Resource + Clone + Debug + DeserializeOwned + Serialize,
S1: ToString,
S2: AsRef<str>,
C: FnOnce(ObjectMeta) -> T,
F: FnOnce(T) -> Result<T, E>,
Eq: FnOnce(&T, &T) -> bool,
E: From<Error>,
{
match api.get(name.as_ref()).await {
Err(Error::Api(ae)) if ae.code == 404 => {
log::debug!("CreateOrUpdate - Err(Api(404))");
let object: T = creator(ObjectMeta {
namespace: namespace.map(|s| s.to_string()),
name: Some(name.as_ref().to_string()),
..Default::default()
});
let object = mutator(object)?;
api.create(&PostParams::default(), &object).await?;
Ok(Outcome::Created(object))
}
Err(e) => {
log::info!("Error - {}", e);
Err(e)?
}
Ok(object) => {
log::debug!("CreateOrUpdate - Ok(...)");
let new_object = mutator(object.clone())?;
if !eq(&object, &new_object) {
log::debug!("CreateOrUpdate - Changed -> replacing");
api.replace(name.as_ref(), &PostParams::default(), &new_object)
.await?;
Ok(Outcome::Updated(new_object))
} else {
Ok(Outcome::Unchanged(new_object))
}
}
}
}
pub async fn create_or_update<T, S1, S2, F, E>(
api: &Api<T>,
namespace: Option<S1>,
name: S2,
mutator: F,
) -> Result<Outcome<T>, E>
where
T: Resource + Clone + Debug + DeserializeOwned + Serialize + PartialEq + Default,
S1: ToString,
S2: AsRef<str>,
F: FnOnce(T) -> Result<T, E>,
E: From<Error>,
{
create_or_update_by(
api,
namespace,
name,
|meta| {
let mut object: T = Default::default();
*object.meta_mut() = meta;
object
},
|this, that| this == that,
mutator,
)
.await
}