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}