1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! High-level utilities for runtime API discovery.

use crate::{Client, Result};
pub use kube_core::discovery::{verbs, ApiCapabilities, ApiResource, Scope};
use kube_core::gvk::GroupVersionKind;
use std::collections::HashMap;
mod apigroup;
pub mod oneshot;
pub use apigroup::ApiGroup;
mod parse;

// re-export one-shots
pub use oneshot::{group, pinned_group, pinned_kind};

/// How the Discovery client decides what api groups to scan
enum DiscoveryMode {
    /// Only allow explicitly listed apigroups
    Allow(Vec<String>),
    /// Allow all apigroups except the ones listed
    Block(Vec<String>),
}

impl DiscoveryMode {
    fn is_queryable(&self, group: &String) -> bool {
        match &self {
            Self::Allow(allowed) => allowed.contains(group),
            Self::Block(blocked) => !blocked.contains(group),
        }
    }
}

/// A caching client for running API discovery against the Kubernetes API.
///
/// This simplifies the required querying and type matching, and stores the responses
/// for each discovered api group and exposes helpers to access them.
///
/// The discovery process varies in complexity depending on:
/// - how much you know about the kind(s) and group(s) you are interested in
/// - how many groups you are interested in
///
/// Discovery can be performed on:
/// - all api groups (default)
/// - a subset of api groups (by setting Discovery::filter)
///
/// To make use of discovered apis, extract one or more [`ApiGroup`]s from it,
/// or resolve a precise one using [`Discovery::resolve_gvk`](crate::discovery::Discovery::resolve_gvk).
///
/// If caching of results is __not required__, then a simpler [`oneshot`](crate::discovery::oneshot) discovery system can be used.
///
/// [`ApiGroup`]: crate::discovery::ApiGroup
#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
pub struct Discovery {
    client: Client,
    groups: HashMap<String, ApiGroup>,
    mode: DiscoveryMode,
}

/// Caching discovery interface
///
/// Builds an internal map of its cache
impl Discovery {
    /// Construct a caching api discovery client
    #[must_use]
    pub fn new(client: Client) -> Self {
        let groups = HashMap::new();
        let mode = DiscoveryMode::Block(vec![]);
        Self { client, groups, mode }
    }

    /// Configure the discovery client to only look for the listed apigroups
    #[must_use]
    pub fn filter(mut self, allow: &[&str]) -> Self {
        self.mode = DiscoveryMode::Allow(allow.iter().map(ToString::to_string).collect());
        self
    }

    /// Configure the discovery client to look for all apigroups except the listed ones
    #[must_use]
    pub fn exclude(mut self, deny: &[&str]) -> Self {
        self.mode = DiscoveryMode::Block(deny.iter().map(ToString::to_string).collect());
        self
    }

    /// Runs or re-runs the configured discovery algorithm and updates/populates the cache
    ///
    /// The cache is empty cleared when this is started. By default, every api group found is checked,
    /// causing `N+2` queries to the api server (where `N` is number of api groups).
    ///
    /// ```no_run
    /// use kube::{Client, api::{Api, DynamicObject}, discovery::{Discovery, verbs, Scope}, ResourceExt};
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = Client::try_default().await?;
    ///     let discovery = Discovery::new(client.clone()).run().await?;
    ///     for group in discovery.groups() {
    ///         for (ar, caps) in group.recommended_resources() {
    ///             if !caps.supports_operation(verbs::LIST) {
    ///                 continue;
    ///             }
    ///             let api: Api<DynamicObject> = Api::all_with(client.clone(), &ar);
    ///             // can now api.list() to emulate kubectl get all --all
    ///             for obj in api.list(&Default::default()).await? {
    ///                 println!("{} {}: {}", ar.api_version, ar.kind, obj.name_any());
    ///             }
    ///         }
    ///     }
    ///     Ok(())
    /// }
    /// ```
    /// See a bigger example in [examples/dynamic.api](https://github.com/kube-rs/kube/blob/main/examples/dynamic_api.rs)
    pub async fn run(mut self) -> Result<Self> {
        self.groups.clear();
        let api_groups = self.client.list_api_groups().await?;
        // query regular groups + crds under /apis
        for g in api_groups.groups {
            let key = g.name.clone();
            if self.mode.is_queryable(&key) {
                let apigroup = ApiGroup::query_apis(&self.client, g).await?;
                self.groups.insert(key, apigroup);
            }
        }
        // query core versions under /api
        let corekey = ApiGroup::CORE_GROUP.to_string();
        if self.mode.is_queryable(&corekey) {
            let coreapis = self.client.list_core_api_versions().await?;
            let apigroup = ApiGroup::query_core(&self.client, coreapis).await?;
            self.groups.insert(corekey, apigroup);
        }
        Ok(self)
    }
}

/// Interface to the Discovery cache
impl Discovery {
    /// Returns iterator over all served groups
    pub fn groups(&self) -> impl Iterator<Item = &ApiGroup> {
        self.groups.values()
    }

    /// Returns a sorted vector of all served groups
    ///
    /// This vector is in kubectl's normal alphabetical group order
    pub fn groups_alphabetical(&self) -> Vec<&ApiGroup> {
        let mut values: Vec<_> = self.groups().collect();
        // collect to maintain kubectl order of groups
        values.sort_by_key(|g| g.name());
        values
    }

    /// Returns the [`ApiGroup`] for a given group if served
    pub fn get(&self, group: &str) -> Option<&ApiGroup> {
        self.groups.get(group)
    }

    /// Check if a group is served by the apiserver
    pub fn has_group(&self, group: &str) -> bool {
        self.groups.contains_key(group)
    }

    /// Finds an [`ApiResource`] and its [`ApiCapabilities`] after discovery by matching a GVK
    ///
    /// This is for quick extraction after having done a complete discovery.
    /// If you are only interested in a single kind, consider [`oneshot::pinned_kind`](crate::discovery::pinned_kind).
    pub fn resolve_gvk(&self, gvk: &GroupVersionKind) -> Option<(ApiResource, ApiCapabilities)> {
        self.get(&gvk.group)?
            .versioned_resources(&gvk.version)
            .into_iter()
            .find(|res| res.0.kind == gvk.kind)
    }
}