use std::fmt::Debug;
use serde::de::DeserializeOwned;
pub trait ComponentizedResource {
type Component: PartialEq + Eq + Debug;
const COMPONENT: Self::Component;
type Resource: kube::Resource<DynamicType: Default> + DeserializeOwned + Clone + Debug;
}
pub trait NamedResource<T: ComponentizedResource> {
fn name(&self) -> String;
fn ns(&self) -> Option<String>;
}
pub trait LabeledResource<T: ComponentizedResource> {
fn selector_all(&self) -> String;
}
pub trait ApiResource<T>
where
T: ComponentizedResource,
<T as ComponentizedResource>::Resource: Sized,
{
fn api(&self, client: kube::Client) -> kube::Api<<T as ComponentizedResource>::Resource>;
}
impl<T, R> ApiResource<T> for R
where
T: ComponentizedResource,
<T as ComponentizedResource>::Resource: kube::Resource + Sized,
<<T as ComponentizedResource>::Resource as kube::Resource>::Scope: api_resource_scope::Dispatch<T>,
R: kube::Resource,
{
fn api(&self, client: kube::Client) -> kube::Api<<T as ComponentizedResource>::Resource> {
<<<T as ComponentizedResource>::Resource as kube::Resource>::Scope as api_resource_scope::Dispatch<T>>::api(
self, client,
)
}
}
mod api_resource_scope {
use super::ComponentizedResource;
pub(super) trait Dispatch<T: ComponentizedResource> {
fn api<R: kube::Resource>(owner: &R, client: kube::Client)
-> kube::Api<<T as ComponentizedResource>::Resource>;
}
impl<T> Dispatch<T> for kube::core::NamespaceResourceScope
where
T: ComponentizedResource,
<T as ComponentizedResource>::Resource: kube::Resource<Scope = kube::core::NamespaceResourceScope>,
{
fn api<R: kube::Resource>(
owner: &R,
client: kube::Client,
) -> kube::Api<<T as ComponentizedResource>::Resource> {
kube::Api::namespaced(client, owner.meta().namespace.as_ref().unwrap())
}
}
impl<T> Dispatch<T> for kube::core::ClusterResourceScope
where
T: ComponentizedResource,
<T as ComponentizedResource>::Resource: kube::Resource<Scope = kube::core::ClusterResourceScope>,
{
fn api<R: kube::Resource>(_: &R, client: kube::Client) -> kube::Api<<T as ComponentizedResource>::Resource> {
kube::Api::all(client)
}
}
}
pub trait ResourceGenerator<T: ComponentizedResource> {
type GenConfig;
type Error;
fn generate(&self, config: Self::GenConfig) -> std::result::Result<T::Resource, Self::Error>;
}
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use k8s_openapi::{
api::core::v1::{Namespace, Pod},
apimachinery::pkg::apis::meta::v1::ObjectMeta,
};
use kube::{CustomResource, client::Body};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::*;
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
#[kube(group = "tests.staircase.dev", version = "v1", kind = "Owner", namespaced)]
struct OwnerSpec {}
struct PodComponent;
impl ComponentizedResource for PodComponent {
type Component = ();
type Resource = Pod;
const COMPONENT: Self::Component = ();
}
struct NamespaceComponent;
impl ComponentizedResource for NamespaceComponent {
type Component = ();
type Resource = Namespace;
const COMPONENT: Self::Component = ();
}
fn dummy_client() -> kube::Client {
let service = tower::service_fn(|_request: http::Request<Body>| async {
Ok::<_, Infallible>(http::Response::new(Body::empty()))
});
kube::Client::new(service, "default")
}
fn owner() -> Owner {
Owner {
metadata: ObjectMeta {
name: Some("sample".to_string()),
namespace: Some("owner-ns".to_string()),
..Default::default()
},
spec: OwnerSpec {},
}
}
#[test]
fn api_resource_uses_owner_namespace_for_namespaced_resources() {
let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
let _guard = rt.enter();
let api = ApiResource::<PodComponent>::api(&owner(), dummy_client());
assert_eq!(api.namespace(), Some("owner-ns"));
assert_eq!(api.resource_url(), "/api/v1/namespaces/owner-ns/pods");
}
#[test]
fn api_resource_uses_all_namespaces_for_cluster_resources() {
let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
let _guard = rt.enter();
let api = ApiResource::<NamespaceComponent>::api(&owner(), dummy_client());
assert_eq!(api.namespace(), None);
assert_eq!(api.resource_url(), "/api/v1/namespaces");
}
}