Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entry API #811

Merged
merged 12 commits into from
Feb 14, 2022
161 changes: 161 additions & 0 deletions kube-client/src/api/entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#![warn(missing_docs)]

use std::fmt::Debug;

use kube_core::{params::PostParams, Resource};
use serde::{de::DeserializeOwned, Serialize};

use crate::{Api, Result};

impl<K: Resource + Clone + DeserializeOwned + Debug> Api<K> {
pub async fn entry<'a>(&'a self, name: &'a str) -> Result<Entry<'a, K>> {
Ok(match self.try_get(name).await? {
Some(object) => Entry::Occupied(OccupiedEntry {
api: self,
object,
dirtiness: Dirtiness::Clean,
}),
None => Entry::Vacant(VacantEntry { api: self, name }),
})
}
}

#[derive(Debug)]
pub enum Entry<'a, K> {
Occupied(OccupiedEntry<'a, K>),
Vacant(VacantEntry<'a, K>),
}

impl<'a, K> Entry<'a, K> {
pub fn get(&self) -> Option<&K> {
match self {
Entry::Occupied(entry) => Some(entry.get()),
Entry::Vacant(_) => None,
}
}

pub fn get_mut(&mut self) -> Option<&mut K> {
match self {
Entry::Occupied(entry) => Some(entry.get_mut()),
Entry::Vacant(_) => None,
}
}

pub fn and_modify(self, f: impl FnOnce(&mut K)) -> Self {
match self {
Entry::Occupied(entry) => Entry::Occupied(entry.and_modify(f)),
entry @ Entry::Vacant(_) => entry,
}
}

pub fn or_insert(self, default: impl FnOnce() -> K) -> OccupiedEntry<'a, K>
where
K: Resource,
{
match self {
Entry::Occupied(entry) => entry,
Entry::Vacant(entry) => entry.insert(default()),
}
}
}

pub struct OccupiedEntry<'a, K> {
api: &'a Api<K>,
dirtiness: Dirtiness,
object: K,
}

#[derive(Debug)]
enum Dirtiness {
Clean,
Dirty,
New,
}

impl<'a, K> OccupiedEntry<'a, K> {
pub fn get(&self) -> &K {
&self.object
}

pub fn get_mut(&mut self) -> &mut K {
self.dirtiness = match self.dirtiness {
Dirtiness::Clean => Dirtiness::Dirty,
Dirtiness::Dirty => Dirtiness::Dirty,
Dirtiness::New => Dirtiness::New,
};
&mut self.object
}

pub fn and_modify(mut self, f: impl FnOnce(&mut K)) -> Self {
f(self.get_mut());
self
}

pub fn into_object(self) -> K {
self.object
}

pub async fn sync(&mut self) -> Result<()>
where
K: Resource + DeserializeOwned + Serialize + Clone + Debug,
{
self.object = match self.dirtiness {
Dirtiness::New => self.api.create(&PostParams::default(), &self.object).await?,
Dirtiness::Dirty => {
self.api
.replace(
self.object.meta().name.as_deref().unwrap(),
&PostParams::default(),
&self.object,
)
.await?
clux marked this conversation as resolved.
Show resolved Hide resolved
}
Dirtiness::Clean => self.api.get(self.object.meta().name.as_deref().unwrap()).await?,
clux marked this conversation as resolved.
Show resolved Hide resolved
};
self.dirtiness = Dirtiness::Clean;
Ok(())
}
}

pub struct VacantEntry<'a, K> {
api: &'a Api<K>,
name: &'a str,
}

impl<'a, K> VacantEntry<'a, K> {
pub fn insert(self, mut object: K) -> OccupiedEntry<'a, K>
where
K: Resource,
{
let meta = object.meta_mut();
meta.name.get_or_insert_with(|| self.name.to_string());
clux marked this conversation as resolved.
Show resolved Hide resolved
if meta.namespace.is_none() {
meta.namespace = self.api.namespace.clone();
}
OccupiedEntry {
api: self.api,
object,
dirtiness: Dirtiness::New,
}
}
}

impl<'a, K: Debug> Debug for OccupiedEntry<'a, K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OccupiedEntry")
.field("api", &"...")
nightkr marked this conversation as resolved.
Show resolved Hide resolved
.field("dirtiness", &self.dirtiness)
.field("object", &self.object)
.finish()
}
}

impl<'a, K> Debug for VacantEntry<'a, K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VacantEntry")
.field("api", &"...")
.field("name", &self.name)
.field("namespace", &self.api.namespace)
.finish()
}
}
34 changes: 10 additions & 24 deletions kube-client/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub use subresource::{Evict, EvictParams, Log, LogParams, ScaleSpec, ScaleStatus

mod util;

mod entry;

// Re-exports from kube-core
#[cfg(feature = "admission")]
#[cfg_attr(docsrs, doc(cfg(feature = "admission")))]
Expand Down Expand Up @@ -46,6 +48,7 @@ pub struct Api<K> {
pub(crate) request: Request,
/// The client to use (from this library)
pub(crate) client: Client,
namespace: Option<String>,
/// Note: Using `iter::Empty` over `PhantomData`, because we never actually keep any
/// `K` objects, so `Empty` better models our constraints (in particular, `Empty<K>`
/// is `Send`, even if `K` may not be).
Expand All @@ -65,6 +68,7 @@ impl<K: Resource> Api<K> {
Self {
client,
request: Request::new(url),
namespace: None,
phantom: std::iter::empty(),
}
}
Expand All @@ -77,6 +81,7 @@ impl<K: Resource> Api<K> {
Self {
client,
request: Request::new(url),
namespace: Some(ns.to_string()),
phantom: std::iter::empty(),
}
}
Expand All @@ -88,12 +93,8 @@ impl<K: Resource> Api<K> {
/// Unless configured explicitly, the default namespace is either "default"
/// out of cluster, or the service account's namespace in cluster.
pub fn default_namespaced_with(client: Client, dyntype: &K::DynamicType) -> Self {
let url = K::url_path(dyntype, Some(client.default_ns()));
Self {
client,
request: Request::new(url),
phantom: std::iter::empty(),
}
let ns = client.default_ns().to_string();
Self::namespaced_with(client, &ns, dyntype)
}

/// Consume self and return the [`Client`]
Expand All @@ -117,35 +118,20 @@ where
{
/// Cluster level resources, or resources viewed across all namespaces
pub fn all(client: Client) -> Self {
let url = K::url_path(&Default::default(), None);
Self {
client,
request: Request::new(url),
phantom: std::iter::empty(),
}
Self::all_with(client, &K::DynamicType::default())
}

/// Namespaced resource within a given namespace
pub fn namespaced(client: Client, ns: &str) -> Self {
let url = K::url_path(&Default::default(), Some(ns));
Self {
client,
request: Request::new(url),
phantom: std::iter::empty(),
}
Self::namespaced_with(client, ns, &K::DynamicType::default())
}

/// Namespaced resource within the default namespace
///
/// Unless configured explicitly, the default namespace is either "default"
/// out of cluster, or the service account's namespace in cluster.
pub fn default_namespaced(client: Client) -> Self {
let url = K::url_path(&Default::default(), Some(client.default_ns()));
Self {
client,
request: Request::new(url),
phantom: std::iter::empty(),
}
Self::default_namespaced_with(client, &K::DynamicType::default())
}
}

Expand Down