kube_client/discovery/
parse.rs

1//! Abstractions on top of k8s_openapi::apimachinery::pkg::apis::meta::v1
2use crate::{error::DiscoveryError, Error, Result};
3use k8s_openapi::apimachinery::pkg::apis::meta::v1::{APIResource, APIResourceList};
4use kube_core::{
5    discovery::{ApiCapabilities, ApiResource, Scope},
6    gvk::{GroupVersion, ParseGroupVersionError},
7};
8
9/// Creates an `ApiResource` from a `meta::v1::APIResource` instance + its groupversion.
10///
11/// Returns a `DiscoveryError` if the passed group_version cannot be parsed
12pub(crate) fn parse_apiresource(
13    ar: &APIResource,
14    group_version: &str,
15) -> Result<ApiResource, ParseGroupVersionError> {
16    let gv: GroupVersion = group_version.parse()?;
17    // NB: not safe to use this with subresources (they don't have api_versions)
18    Ok(ApiResource {
19        group: ar.group.clone().unwrap_or_else(|| gv.group.clone()),
20        version: ar.version.clone().unwrap_or_else(|| gv.version.clone()),
21        api_version: gv.api_version(),
22        kind: ar.kind.to_string(),
23        plural: ar.name.clone(),
24    })
25}
26
27/// Creates `ApiCapabilities` from a `meta::v1::APIResourceList` instance + a name from the list.
28///
29/// Returns a `DiscoveryError` if the list does not contain resource with passed `name`.
30pub(crate) fn parse_apicapabilities(list: &APIResourceList, name: &str) -> Result<ApiCapabilities> {
31    let ar = list
32        .resources
33        .iter()
34        .find(|r| r.name == name)
35        .ok_or_else(|| Error::Discovery(DiscoveryError::MissingResource(name.into())))?;
36    let scope = if ar.namespaced {
37        Scope::Namespaced
38    } else {
39        Scope::Cluster
40    };
41
42    let subresource_name_prefix = format!("{name}/");
43    let mut subresources = vec![];
44    for res in &list.resources {
45        if let Some(subresource_name) = res.name.strip_prefix(&subresource_name_prefix) {
46            let mut api_resource =
47                parse_apiresource(res, &list.group_version).map_err(|ParseGroupVersionError(s)| {
48                    Error::Discovery(DiscoveryError::InvalidGroupVersion(s))
49                })?;
50            api_resource.plural = subresource_name.to_string();
51            let caps = parse_apicapabilities(list, &res.name)?; // NB: recursion
52            subresources.push((api_resource, caps));
53        }
54    }
55    Ok(ApiCapabilities {
56        scope,
57        subresources,
58        operations: ar.verbs.clone(),
59    })
60}
61
62/// Internal resource information and capabilities for a particular ApiGroup at a particular version
63pub(crate) struct GroupVersionData {
64    /// Pinned api version
65    pub(crate) version: String,
66    /// Pair of dynamic resource info along with what it supports.
67    pub(crate) resources: Vec<(ApiResource, ApiCapabilities)>,
68}
69
70impl GroupVersionData {
71    /// Given an APIResourceList, extract all information for a given version
72    pub(crate) fn new(version: String, list: APIResourceList) -> Result<Self> {
73        let mut resources = vec![];
74        for res in &list.resources {
75            // skip subresources
76            if res.name.contains('/') {
77                continue;
78            }
79            // NB: these two should be infallible from discovery when k8s api is well-behaved, but..
80            let ar = parse_apiresource(res, &list.group_version).map_err(|ParseGroupVersionError(s)| {
81                Error::Discovery(DiscoveryError::InvalidGroupVersion(s))
82            })?;
83            let caps = parse_apicapabilities(&list, &res.name)?;
84            resources.push((ar, caps));
85        }
86        Ok(GroupVersionData { version, resources })
87    }
88}