Skip to content

Commit

Permalink
Initial Entry API sketch
Browse files Browse the repository at this point in the history
Fixes #810
  • Loading branch information
nightkr committed Feb 7, 2022
1 parent bf21fbf commit 9b1c932
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 24 deletions.
149 changes: 149 additions & 0 deletions kube-client/src/api/entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#![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,
new: false,
}),
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>,
new: bool,
object: K,
}

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

pub fn get_mut(&mut self) -> &mut K {
&mut self.object
}

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

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

pub async fn sync(&mut self) -> Result<()>
where
K: Resource + DeserializeOwned + Serialize + Clone + Debug,
{
if self.new {
self.new = false;
self.object = self.api.create(&PostParams::default(), &self.object).await?;
Ok(())
} else {
self.object = self
.api
.replace(
self.object.meta().name.as_deref().unwrap(),
&PostParams::default(),
&self.object,
)
.await?;
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());
if meta.namespace.is_none() {
meta.namespace = self.api.namespace.clone();
}
OccupiedEntry {
api: self.api,
object,
new: true,
}
}
}

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", &"...")
.field("new", &self.new)
.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

0 comments on commit 9b1c932

Please sign in to comment.