pub use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference;
use once_cell::sync::Lazy;
use std::{borrow::Cow, collections::BTreeMap};
pub trait Resource {
type DynamicType: Send + Sync + 'static;
fn kind(dt: &Self::DynamicType) -> Cow<'_, str>;
fn group(dt: &Self::DynamicType) -> Cow<'_, str>;
fn version(dt: &Self::DynamicType) -> Cow<'_, str>;
fn api_version(dt: &Self::DynamicType) -> Cow<'_, str> {
let group = Self::group(dt);
if group.is_empty() {
return Self::version(dt);
}
let mut group = group.into_owned();
group.push('/');
group.push_str(&Self::version(dt));
group.into()
}
fn plural(dt: &Self::DynamicType) -> Cow<'_, str> {
to_plural(&Self::kind(dt).to_ascii_lowercase()).into()
}
fn url_path(dt: &Self::DynamicType, namespace: Option<&str>) -> String {
let n = if let Some(ns) = namespace {
format!("namespaces/{}/", ns)
} else {
"".into()
};
let group = Self::group(dt);
let api_version = Self::api_version(dt);
let plural = Self::plural(dt);
format!(
"/{group}/{api_version}/{namespaces}{plural}",
group = if group.is_empty() { "api" } else { "apis" },
api_version = api_version,
namespaces = n,
plural = plural
)
}
fn meta(&self) -> &ObjectMeta;
fn meta_mut(&mut self) -> &mut ObjectMeta;
}
impl<K> Resource for K
where
K: k8s_openapi::Metadata<Ty = ObjectMeta>,
{
type DynamicType = ();
fn kind(_: &()) -> Cow<'_, str> {
K::KIND.into()
}
fn group(_: &()) -> Cow<'_, str> {
K::GROUP.into()
}
fn version(_: &()) -> Cow<'_, str> {
K::VERSION.into()
}
fn api_version(_: &()) -> Cow<'_, str> {
K::API_VERSION.into()
}
fn meta(&self) -> &ObjectMeta {
self.metadata()
}
fn meta_mut(&mut self) -> &mut ObjectMeta {
self.metadata_mut()
}
}
pub trait ResourceExt: Resource {
fn name(&self) -> String;
fn namespace(&self) -> Option<String>;
fn resource_version(&self) -> Option<String>;
fn uid(&self) -> Option<String>;
fn labels(&self) -> &BTreeMap<String, String>;
fn labels_mut(&mut self) -> &mut BTreeMap<String, String>;
fn annotations(&self) -> &BTreeMap<String, String>;
fn annotations_mut(&mut self) -> &mut BTreeMap<String, String>;
fn owner_references(&self) -> &[OwnerReference];
fn owner_references_mut(&mut self) -> &mut Vec<OwnerReference>;
fn finalizers(&self) -> &[String];
fn finalizers_mut(&mut self) -> &mut Vec<String>;
}
static EMPTY_MAP: Lazy<BTreeMap<String, String>> = Lazy::new(BTreeMap::new);
impl<K: Resource> ResourceExt for K {
fn name(&self) -> String {
self.meta().name.clone().expect(".metadata.name missing")
}
fn namespace(&self) -> Option<String> {
self.meta().namespace.clone()
}
fn resource_version(&self) -> Option<String> {
self.meta().resource_version.clone()
}
fn uid(&self) -> Option<String> {
self.meta().uid.clone()
}
fn labels(&self) -> &BTreeMap<String, String> {
self.meta().labels.as_ref().unwrap_or_else(|| &*EMPTY_MAP)
}
fn labels_mut(&mut self) -> &mut BTreeMap<String, String> {
self.meta_mut().labels.get_or_insert_with(BTreeMap::new)
}
fn annotations(&self) -> &BTreeMap<String, String> {
self.meta().annotations.as_ref().unwrap_or_else(|| &*EMPTY_MAP)
}
fn annotations_mut(&mut self) -> &mut BTreeMap<String, String> {
self.meta_mut().annotations.get_or_insert_with(BTreeMap::new)
}
fn owner_references(&self) -> &[OwnerReference] {
self.meta().owner_references.as_deref().unwrap_or_default()
}
fn owner_references_mut(&mut self) -> &mut Vec<OwnerReference> {
self.meta_mut().owner_references.get_or_insert_with(Vec::new)
}
fn finalizers(&self) -> &[String] {
self.meta().finalizers.as_deref().unwrap_or_default()
}
fn finalizers_mut(&mut self) -> &mut Vec<String> {
self.meta_mut().finalizers.get_or_insert_with(Vec::new)
}
}
pub(crate) fn to_plural(word: &str) -> String {
if word == "endpoints" || word == "endpointslices" {
return word.to_owned();
} else if word == "nodemetrics" {
return "nodes".to_owned();
} else if word == "podmetrics" {
return "pods".to_owned();
}
if word.ends_with('s')
|| word.ends_with('x')
|| word.ends_with('z')
|| word.ends_with("ch")
|| word.ends_with("sh")
{
return format!("{}es", word);
}
if word.ends_with('y') {
if let Some(c) = word.chars().nth(word.len() - 2) {
if !matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') {
let mut chars = word.chars();
chars.next_back();
return format!("{}ies", chars.as_str());
}
}
}
format!("{}s", word)
}
#[test]
fn test_to_plural_native() {
#[rustfmt::skip]
let native_kinds = vec![
("APIService", "apiservices"),
("Binding", "bindings"),
("CertificateSigningRequest", "certificatesigningrequests"),
("ClusterRole", "clusterroles"), ("ClusterRoleBinding", "clusterrolebindings"),
("ComponentStatus", "componentstatuses"),
("ConfigMap", "configmaps"),
("ControllerRevision", "controllerrevisions"),
("CronJob", "cronjobs"),
("CSIDriver", "csidrivers"), ("CSINode", "csinodes"), ("CSIStorageCapacity", "csistoragecapacities"),
("CustomResourceDefinition", "customresourcedefinitions"),
("DaemonSet", "daemonsets"),
("Deployment", "deployments"),
("Endpoints", "endpoints"), ("EndpointSlice", "endpointslices"),
("Event", "events"),
("FlowSchema", "flowschemas"),
("HorizontalPodAutoscaler", "horizontalpodautoscalers"),
("Ingress", "ingresses"), ("IngressClass", "ingressclasses"),
("Job", "jobs"),
("Lease", "leases"),
("LimitRange", "limitranges"),
("LocalSubjectAccessReview", "localsubjectaccessreviews"),
("MutatingWebhookConfiguration", "mutatingwebhookconfigurations"),
("Namespace", "namespaces"),
("NetworkPolicy", "networkpolicies"),
("Node", "nodes"),
("PersistentVolumeClaim", "persistentvolumeclaims"),
("PersistentVolume", "persistentvolumes"),
("PodDisruptionBudget", "poddisruptionbudgets"),
("Pod", "pods"),
("PodSecurityPolicy", "podsecuritypolicies"),
("PodTemplate", "podtemplates"),
("PriorityClass", "priorityclasses"),
("PriorityLevelConfiguration", "prioritylevelconfigurations"),
("ReplicaSet", "replicasets"),
("ReplicationController", "replicationcontrollers"),
("ResourceQuota", "resourcequotas"),
("Role", "roles"), ("RoleBinding", "rolebindings"),
("RuntimeClass", "runtimeclasses"),
("Secret", "secrets"),
("SelfSubjectAccessReview", "selfsubjectaccessreviews"),
("SelfSubjectRulesReview", "selfsubjectrulesreviews"),
("ServiceAccount", "serviceaccounts"),
("Service", "services"),
("StatefulSet", "statefulsets"),
("StorageClass", "storageclasses"), ("StorageVersion", "storageversions"),
("SubjectAccessReview", "subjectaccessreviews"),
("TokenReview", "tokenreviews"),
("ValidatingWebhookConfiguration", "validatingwebhookconfigurations"),
("VolumeAttachment", "volumeattachments"),
];
for (kind, plural) in native_kinds {
assert_eq!(to_plural(&kind.to_ascii_lowercase()), plural);
}
}