kube_core/
discovery.rs

1//! Type information structs for API discovery
2use crate::{gvk::GroupVersionKind, resource::Resource};
3use serde::{Deserialize, Serialize};
4
5/// Information about a Kubernetes API resource
6///
7/// Enough information to use it like a `Resource` by passing it to the dynamic `Api`
8/// constructors like `Api::all_with` and `Api::namespaced_with`.
9#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
10pub struct ApiResource {
11    /// Resource group, empty for core group.
12    pub group: String,
13    /// group version
14    pub version: String,
15    /// apiVersion of the resource (v1 for core group,
16    /// groupName/groupVersions for other).
17    pub api_version: String,
18    /// Singular PascalCase name of the resource
19    pub kind: String,
20    /// Plural name of the resource
21    pub plural: String,
22}
23
24impl ApiResource {
25    /// Creates an ApiResource by type-erasing a Resource
26    pub fn erase<K: Resource>(dt: &K::DynamicType) -> Self {
27        ApiResource {
28            group: K::group(dt).to_string(),
29            version: K::version(dt).to_string(),
30            api_version: K::api_version(dt).to_string(),
31            kind: K::kind(dt).to_string(),
32            plural: K::plural(dt).to_string(),
33        }
34    }
35
36    /// Creates an ApiResource from group, version, kind and plural name.
37    pub fn from_gvk_with_plural(gvk: &GroupVersionKind, plural: &str) -> Self {
38        ApiResource {
39            api_version: gvk.api_version(),
40            group: gvk.group.clone(),
41            version: gvk.version.clone(),
42            kind: gvk.kind.clone(),
43            plural: plural.to_string(),
44        }
45    }
46
47    /// Creates an ApiResource from group, version and kind.
48    ///
49    /// # Warning
50    /// This function will **guess** the resource plural name.
51    /// Usually, this is ok, but for CRDs with complex pluralisations it can fail.
52    /// If you are getting your values from `kube_derive` use the generated method for giving you an [`ApiResource`].
53    /// Otherwise consider using [`ApiResource::from_gvk_with_plural`](crate::discovery::ApiResource::from_gvk_with_plural)
54    /// to explicitly set the plural, or run api discovery on it via `kube::discovery`.
55    pub fn from_gvk(gvk: &GroupVersionKind) -> Self {
56        ApiResource::from_gvk_with_plural(gvk, &to_plural(&gvk.kind.to_ascii_lowercase()))
57    }
58}
59
60/// Resource scope
61#[derive(Debug, Clone, Hash, Eq, PartialEq)]
62pub enum Scope {
63    /// Objects are global
64    Cluster,
65    /// Each object lives in namespace.
66    Namespaced,
67}
68
69/// Rbac verbs for ApiCapabilities
70pub mod verbs {
71    /// Create a resource
72    pub const CREATE: &str = "create";
73    /// Get single resource
74    pub const GET: &str = "get";
75    /// List objects
76    pub const LIST: &str = "list";
77    /// Watch for objects changes
78    pub const WATCH: &str = "watch";
79    /// Delete single object
80    pub const DELETE: &str = "delete";
81    /// Delete multiple objects at once
82    pub const DELETE_COLLECTION: &str = "deletecollection";
83    /// Update an object
84    pub const UPDATE: &str = "update";
85    /// Patch an object
86    pub const PATCH: &str = "patch";
87}
88
89/// Contains the capabilities of an API resource
90#[derive(Debug, Clone)]
91pub struct ApiCapabilities {
92    /// Scope of the resource
93    pub scope: Scope,
94    /// Available subresources.
95    ///
96    /// Please note that returned ApiResources are not standalone resources.
97    /// Their name will be of form `subresource_name`, not `resource_name/subresource_name`.
98    /// To work with subresources, use `Request` methods for now.
99    pub subresources: Vec<(ApiResource, ApiCapabilities)>,
100    /// Supported operations on this resource
101    pub operations: Vec<String>,
102}
103
104impl ApiCapabilities {
105    /// Checks that given verb is supported on this resource.
106    pub fn supports_operation(&self, operation: &str) -> bool {
107        self.operations.iter().any(|op| op == operation)
108    }
109}
110
111// Simple pluralizer. Handles the special cases.
112fn to_plural(word: &str) -> String {
113    if word == "endpoints" || word == "endpointslices" {
114        return word.to_owned();
115    } else if word == "nodemetrics" {
116        return "nodes".to_owned();
117    } else if word == "podmetrics" {
118        return "pods".to_owned();
119    }
120
121    // Words ending in s, x, z, ch, sh will be pluralized with -es (eg. foxes).
122    if word.ends_with('s')
123        || word.ends_with('x')
124        || word.ends_with('z')
125        || word.ends_with("ch")
126        || word.ends_with("sh")
127    {
128        return format!("{word}es");
129    }
130
131    // Words ending in y that are preceded by a consonant will be pluralized by
132    // replacing y with -ies (eg. puppies).
133    if word.ends_with('y') {
134        if let Some(c) = word.chars().nth(word.len() - 2) {
135            if !matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') {
136                // Remove 'y' and add `ies`
137                let mut chars = word.chars();
138                chars.next_back();
139                return format!("{}ies", chars.as_str());
140            }
141        }
142    }
143
144    // All other words will have "s" added to the end (eg. days).
145    format!("{word}s")
146}
147
148#[test]
149fn test_to_plural_native() {
150    // Extracted from `swagger.json`
151    #[rustfmt::skip]
152    let native_kinds = vec![
153        ("APIService", "apiservices"),
154        ("Binding", "bindings"),
155        ("CertificateSigningRequest", "certificatesigningrequests"),
156        ("ClusterRole", "clusterroles"), ("ClusterRoleBinding", "clusterrolebindings"),
157        ("ComponentStatus", "componentstatuses"),
158        ("ConfigMap", "configmaps"),
159        ("ControllerRevision", "controllerrevisions"),
160        ("CronJob", "cronjobs"),
161        ("CSIDriver", "csidrivers"), ("CSINode", "csinodes"), ("CSIStorageCapacity", "csistoragecapacities"),
162        ("CustomResourceDefinition", "customresourcedefinitions"),
163        ("DaemonSet", "daemonsets"),
164        ("Deployment", "deployments"),
165        ("Endpoints", "endpoints"), ("EndpointSlice", "endpointslices"),
166        ("Event", "events"),
167        ("FlowSchema", "flowschemas"),
168        ("HorizontalPodAutoscaler", "horizontalpodautoscalers"),
169        ("Ingress", "ingresses"), ("IngressClass", "ingressclasses"),
170        ("Job", "jobs"),
171        ("Lease", "leases"),
172        ("LimitRange", "limitranges"),
173        ("LocalSubjectAccessReview", "localsubjectaccessreviews"),
174        ("MutatingWebhookConfiguration", "mutatingwebhookconfigurations"),
175        ("Namespace", "namespaces"),
176        ("NetworkPolicy", "networkpolicies"),
177        ("Node", "nodes"),
178        ("PersistentVolumeClaim", "persistentvolumeclaims"),
179        ("PersistentVolume", "persistentvolumes"),
180        ("PodDisruptionBudget", "poddisruptionbudgets"),
181        ("Pod", "pods"),
182        ("PodSecurityPolicy", "podsecuritypolicies"),
183        ("PodTemplate", "podtemplates"),
184        ("PriorityClass", "priorityclasses"),
185        ("PriorityLevelConfiguration", "prioritylevelconfigurations"),
186        ("ReplicaSet", "replicasets"),
187        ("ReplicationController", "replicationcontrollers"),
188        ("ResourceQuota", "resourcequotas"),
189        ("Role", "roles"), ("RoleBinding", "rolebindings"),
190        ("RuntimeClass", "runtimeclasses"),
191        ("Secret", "secrets"),
192        ("SelfSubjectAccessReview", "selfsubjectaccessreviews"),
193        ("SelfSubjectRulesReview", "selfsubjectrulesreviews"),
194        ("ServiceAccount", "serviceaccounts"),
195        ("Service", "services"),
196        ("StatefulSet", "statefulsets"),
197        ("StorageClass", "storageclasses"), ("StorageVersion", "storageversions"),
198        ("SubjectAccessReview", "subjectaccessreviews"),
199        ("TokenReview", "tokenreviews"),
200        ("ValidatingWebhookConfiguration", "validatingwebhookconfigurations"),
201        ("VolumeAttachment", "volumeattachments"),
202    ];
203    for (kind, plural) in native_kinds {
204        assert_eq!(to_plural(&kind.to_ascii_lowercase()), plural);
205    }
206}