Skip to main content

kube_core/discovery/
mod.rs

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