1use std::collections::BTreeSet;
6use std::fmt;
7use std::fs;
8use std::io;
9use std::path::Path;
10use std::path::PathBuf;
11use std::time;
12
13use k8s_openapi_ext as k8s;
14use kube::api;
15use kube::discovery;
16use kube_client_ext::KubeClientExt;
17use serde_json as json;
18use serde_yaml as yaml;
19use tracing::debug;
20use tracing::error;
21use tracing::info;
22use tracing::trace;
23
24use k8s::authenticationv1;
25use k8s::authorizationv1;
26use k8s::corev1;
27use k8s::metav1;
28use k8s::rbacv1;
29
30use rkubectl_features::Feature;
31
32pub use cache::Cache;
33pub use cascade::Cascade;
34pub use dryrun::DryRun;
35pub use namespace::Namespace;
36pub use options::KubeConfigOptions;
37pub use options::KubeapiOptions;
38
39mod apis;
40mod cache;
41mod cascade;
42mod dryrun;
43mod features;
44mod info;
45mod kubeconfig;
46mod namespace;
47mod options;
48mod params;
49mod raw;
50mod server;
51mod version;
52
53#[derive(Clone, Debug)]
56pub struct Kubeapi {
57 config: kube::Config,
58 kubeconfig: kube::config::Kubeconfig,
59 cache: Cache,
60 namespace: Namespace,
61 debug: bool,
62 options: KubeapiOptions,
63}
64
65impl Kubeapi {
66 pub async fn new(
67 config: &KubeConfigOptions,
68 options: &KubeapiOptions,
69 debug: bool,
70 ) -> kube::Result<Self> {
71 let config_options = config.kube_config_options();
72 let options = options.clone();
73 let namespace = default();
74 let cache = cache::Cache::default();
75 Self::kubeconfig(config_options, debug)
76 .await
77 .inspect_err(|err| error!(%err, "from_kubeconfig"))
78 .map(|(config, kubeconfig)| Self {
79 config,
80 kubeconfig,
81 cache,
82 namespace,
83 debug,
84 options,
85 })
86 .and_then(Self::try_load_cache)
87 .map_err(|_| kube::Error::LinesCodecMaxLineLengthExceeded)
88 }
89
90 pub fn cluster_url(&self) -> String {
91 self.config.cluster_url.to_string()
92 }
93
94 pub fn debug(&self, item: impl fmt::Debug) {
95 if self.debug {
96 info!("{item:?}")
97 }
98 }
99
100 pub fn client(&self) -> kube::Result<kube::Client> {
102 kube::Client::try_from(self.config.clone())
103 }
104
105 fn cache_path(&self) -> Result<PathBuf, kube::config::KubeconfigError> {
107 self.options.discovery_cache_for_config(&self.config)
108 }
109
110 fn try_load_cache(self) -> Result<Self, kube::config::KubeconfigError> {
111 let path = self.cache_path()?;
112 let cache = self.cache.try_load(path);
113 if self.debug {
114 info!("Loading cache took {:?}", cache.took());
115 }
116 Ok(Self { cache, ..self })
117 }
118
119 pub fn with_namespace(self, namespace: Namespace) -> Self {
122 Self { namespace, ..self }
123 }
124
125 pub fn namespace(&self) -> &Namespace {
127 &self.namespace
128 }
129
130 pub fn show_namespace(&self) -> bool {
131 matches!(self.namespace, Namespace::All)
132 }
133
134 pub fn cached_server_api_resources(&self) -> Vec<metav1::APIResourceList> {
135 self.cache.api_resources().unwrap_or_default()
136 }
137
138 pub async fn server_preferred_resources(&self) -> kube::Result<Vec<metav1::APIResourceList>> {
139 let ag = self.server_api_groups().await?;
140 let preferred_versions = ag
141 .groups
142 .into_iter()
143 .map(|mut group| {
144 group
145 .preferred_version
146 .unwrap_or_else(|| group.versions.remove(0))
147 })
148 .map(|gv| gv.group_version)
149 .collect::<BTreeSet<_>>();
150 let resources = self
151 .server_api_resources()
152 .await?
153 .into_iter()
154 .filter(|arl| preferred_versions.contains(&arl.group_version))
155 .collect();
156 Ok(resources)
157 }
158
159 pub async fn api_versions(
160 &self,
161 ) -> kube::Result<impl Iterator<Item = metav1::GroupVersionForDiscovery>> {
162 let items = self
163 .server_api_groups()
164 .await?
165 .groups
166 .into_iter()
167 .flat_map(|group| group.versions.into_iter());
168
169 Ok(items)
170 }
171
172 pub fn dynamic_object_api(
173 &self,
174 scope: discovery::Scope,
175 dyntype: &discovery::ApiResource,
176 ) -> kube::Result<api::Api<api::DynamicObject>> {
177 trace!(?scope, ?dyntype, "dynamic_object_api");
178 let client = self.client()?;
179 let dynamic_api = match scope {
180 discovery::Scope::Cluster => api::Api::all_with(client, dyntype),
181 discovery::Scope::Namespaced => match &self.namespace {
182 Namespace::All => api::Api::all_with(client, dyntype),
183 Namespace::Default => api::Api::default_namespaced_with(client, dyntype),
184 Namespace::Namespace(ns) => api::Api::namespaced_with(client, ns, dyntype),
185 },
186 };
187
188 Ok(dynamic_api)
189 }
190
191 pub fn inspect<K>(&self, k: &K)
192 where
193 K: serde::Serialize,
194 {
195 if self.debug {
196 let k = yaml::to_string(k).unwrap_or_default();
197 info!("{k}");
198 }
199 }
200
201 pub fn inspect_err(&self, err: &kube::Error) {
202 if self.debug {
203 info!("{err:?}");
204 }
205 }
206
207 pub fn full_name<K>(&self, k: &K) -> String
208 where
209 K: kube::Resource + kube::ResourceExt,
210 <K as kube::Resource>::DynamicType: Default,
211 {
212 let kind = K::kind(&default()).to_lowercase();
213 let name = k.name_any();
214 format!("{kind}/{name}")
215 }
216}
217
218impl Kubeapi {
219 pub fn local() -> Self {
224 let config = kube::Config::new("http://localhost:6443".parse().unwrap());
225 Self {
226 config,
227 kubeconfig: default(),
228 cache: default(),
229 namespace: default(),
230 debug: default(),
231 options: default(),
232 }
233 }
234}
235
236fn default<T: Default>() -> T {
237 T::default()
238}