kube_client/discovery/
mod.rs

1//! High-level utilities for runtime API discovery.
2
3use crate::{Client, Result};
4pub use kube_core::discovery::{verbs, ApiCapabilities, ApiResource, Scope};
5use kube_core::gvk::GroupVersionKind;
6use std::collections::HashMap;
7mod apigroup;
8pub mod oneshot;
9pub use apigroup::ApiGroup;
10mod parse;
11
12// re-export one-shots
13pub use oneshot::{group, pinned_group, pinned_kind};
14
15/// How the Discovery client decides what api groups to scan
16enum DiscoveryMode {
17    /// Only allow explicitly listed apigroups
18    Allow(Vec<String>),
19    /// Allow all apigroups except the ones listed
20    Block(Vec<String>),
21}
22
23impl DiscoveryMode {
24    fn is_queryable(&self, group: &String) -> bool {
25        match &self {
26            Self::Allow(allowed) => allowed.contains(group),
27            Self::Block(blocked) => !blocked.contains(group),
28        }
29    }
30}
31
32/// A caching client for running API discovery against the Kubernetes API.
33///
34/// This simplifies the required querying and type matching, and stores the responses
35/// for each discovered api group and exposes helpers to access them.
36///
37/// The discovery process varies in complexity depending on:
38/// - how much you know about the kind(s) and group(s) you are interested in
39/// - how many groups you are interested in
40///
41/// Discovery can be performed on:
42/// - all api groups (default)
43/// - a subset of api groups (by setting Discovery::filter)
44///
45/// To make use of discovered apis, extract one or more [`ApiGroup`]s from it,
46/// or resolve a precise one using [`Discovery::resolve_gvk`](crate::discovery::Discovery::resolve_gvk).
47///
48/// If caching of results is __not required__, then a simpler [`oneshot`](crate::discovery::oneshot) discovery system can be used.
49///
50/// [`ApiGroup`]: crate::discovery::ApiGroup
51#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
52pub struct Discovery {
53    client: Client,
54    groups: HashMap<String, ApiGroup>,
55    mode: DiscoveryMode,
56}
57
58/// Caching discovery interface
59///
60/// Builds an internal map of its cache
61impl Discovery {
62    /// Construct a caching api discovery client
63    #[must_use]
64    pub fn new(client: Client) -> Self {
65        let groups = HashMap::new();
66        let mode = DiscoveryMode::Block(vec![]);
67        Self { client, groups, mode }
68    }
69
70    /// Configure the discovery client to only look for the listed apigroups
71    #[must_use]
72    pub fn filter(mut self, allow: &[&str]) -> Self {
73        self.mode = DiscoveryMode::Allow(allow.iter().map(ToString::to_string).collect());
74        self
75    }
76
77    /// Configure the discovery client to look for all apigroups except the listed ones
78    #[must_use]
79    pub fn exclude(mut self, deny: &[&str]) -> Self {
80        self.mode = DiscoveryMode::Block(deny.iter().map(ToString::to_string).collect());
81        self
82    }
83
84    /// Runs or re-runs the configured discovery algorithm and updates/populates the cache
85    ///
86    /// The cache is empty cleared when this is started. By default, every api group found is checked,
87    /// causing `N+2` queries to the api server (where `N` is number of api groups).
88    ///
89    /// ```no_run
90    /// use kube::{Client, api::{Api, DynamicObject}, discovery::{Discovery, verbs, Scope}, ResourceExt};
91    /// #[tokio::main]
92    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
93    ///     let client = Client::try_default().await?;
94    ///     let discovery = Discovery::new(client.clone()).run().await?;
95    ///     for group in discovery.groups() {
96    ///         for (ar, caps) in group.recommended_resources() {
97    ///             if !caps.supports_operation(verbs::LIST) {
98    ///                 continue;
99    ///             }
100    ///             let api: Api<DynamicObject> = Api::all_with(client.clone(), &ar);
101    ///             // can now api.list() to emulate kubectl get all --all
102    ///             for obj in api.list(&Default::default()).await? {
103    ///                 println!("{} {}: {}", ar.api_version, ar.kind, obj.name_any());
104    ///             }
105    ///         }
106    ///     }
107    ///     Ok(())
108    /// }
109    /// ```
110    /// See a bigger example in [examples/dynamic.api](https://github.com/kube-rs/kube/blob/main/examples/dynamic_api.rs)
111    pub async fn run(mut self) -> Result<Self> {
112        self.groups.clear();
113        let api_groups = self.client.list_api_groups().await?;
114        // query regular groups + crds under /apis
115        for g in api_groups.groups {
116            let key = g.name.clone();
117            if self.mode.is_queryable(&key) {
118                let apigroup = ApiGroup::query_apis(&self.client, g).await?;
119                self.groups.insert(key, apigroup);
120            }
121        }
122        // query core versions under /api
123        let corekey = ApiGroup::CORE_GROUP.to_string();
124        if self.mode.is_queryable(&corekey) {
125            let coreapis = self.client.list_core_api_versions().await?;
126            let apigroup = ApiGroup::query_core(&self.client, coreapis).await?;
127            self.groups.insert(corekey, apigroup);
128        }
129        Ok(self)
130    }
131}
132
133/// Interface to the Discovery cache
134impl Discovery {
135    /// Returns iterator over all served groups
136    pub fn groups(&self) -> impl Iterator<Item = &ApiGroup> {
137        self.groups.values()
138    }
139
140    /// Returns a sorted vector of all served groups
141    ///
142    /// This vector is in kubectl's normal alphabetical group order
143    pub fn groups_alphabetical(&self) -> Vec<&ApiGroup> {
144        let mut values: Vec<_> = self.groups().collect();
145        // collect to maintain kubectl order of groups
146        values.sort_by_key(|g| g.name());
147        values
148    }
149
150    /// Returns the [`ApiGroup`] for a given group if served
151    pub fn get(&self, group: &str) -> Option<&ApiGroup> {
152        self.groups.get(group)
153    }
154
155    /// Check if a group is served by the apiserver
156    pub fn has_group(&self, group: &str) -> bool {
157        self.groups.contains_key(group)
158    }
159
160    /// Finds an [`ApiResource`] and its [`ApiCapabilities`] after discovery by matching a GVK
161    ///
162    /// This is for quick extraction after having done a complete discovery.
163    /// If you are only interested in a single kind, consider [`oneshot::pinned_kind`](crate::discovery::pinned_kind).
164    pub fn resolve_gvk(&self, gvk: &GroupVersionKind) -> Option<(ApiResource, ApiCapabilities)> {
165        self.get(&gvk.group)?
166            .versioned_resources(&gvk.version)
167            .into_iter()
168            .find(|res| res.0.kind == gvk.kind)
169    }
170}