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::trace;
22
23use k8s::authenticationv1;
24use k8s::authorizationv1;
25use k8s::corev1;
26use k8s::metav1;
27use k8s::rbacv1;
28
29use rkubectl_features::Feature;
30
31pub use cache::Cache;
32pub use cascade::Cascade;
33pub use dryrun::DryRun;
34pub use namespace::Namespace;
35pub use options::KubeConfigOptions;
36pub use options::KubeapiOptions;
37
38mod cache;
39mod cascade;
40mod dryrun;
41mod features;
42mod info;
43mod kubeconfig;
44mod namespace;
45mod options;
46mod raw;
47mod server;
48mod version;
49
50#[derive(Debug)]
53pub struct Kubeapi {
54 config: kube::Config,
55 kubeconfig: kube::config::Kubeconfig,
56 cache: Cache,
57 namespace: Namespace,
58 debug: bool,
59 options: KubeapiOptions,
60}
61
62impl Kubeapi {
63 pub async fn new(
64 config: &KubeConfigOptions,
65 options: &KubeapiOptions,
66 debug: bool,
67 ) -> kube::Result<Self> {
68 let config_options = config.kube_config_options();
69 let options = options.clone();
70 let namespace = default();
71 let cache = cache::Cache::default();
72 Self::kubeconfig(config_options, debug)
73 .await
74 .inspect_err(|err| error!(%err, "from_kubeconfig"))
75 .map(|(config, kubeconfig)| Self {
76 config,
77 kubeconfig,
78 cache,
79 namespace,
80 debug,
81 options,
82 })
83 .and_then(Self::try_load_cache)
84 .map_err(|_| kube::Error::LinesCodecMaxLineLengthExceeded)
85 }
86
87 pub fn cluster_url(&self) -> String {
88 self.config.cluster_url.to_string()
89 }
90
91 pub fn debug(&self, item: impl fmt::Debug) {
92 if self.debug {
93 println!("{item:?}")
94 }
95 }
96
97 pub fn client(&self) -> kube::Result<kube::Client> {
99 kube::Client::try_from(self.config.clone())
100 }
101
102 fn cache_path(&self) -> Result<PathBuf, kube::config::KubeconfigError> {
104 self.options.discovery_cache_for_config(&self.config)
105 }
106
107 fn try_load_cache(self) -> Result<Self, kube::config::KubeconfigError> {
108 let path = self.cache_path()?;
109 let cache = self.cache.try_load(path);
110 if self.debug {
111 println!("Loading cache took {:?}", cache.took());
112 }
113 Ok(Self { cache, ..self })
114 }
115
116 pub fn with_namespace(self, namespace: Namespace) -> Self {
119 Self { namespace, ..self }
120 }
121
122 pub fn namespace(&self) -> &Namespace {
124 &self.namespace
125 }
126
127 pub fn show_namespace(&self) -> bool {
128 matches!(self.namespace, Namespace::All)
129 }
130
131 pub fn cached_server_api_resources(&self) -> Vec<metav1::APIResourceList> {
132 self.cache.api_resources().unwrap_or_default()
133 }
134
135 pub async fn server_preferred_resources(&self) -> kube::Result<Vec<metav1::APIResourceList>> {
136 let ag = self.server_api_groups().await?;
137 let preferred_versions = ag
138 .groups
139 .into_iter()
140 .map(|mut group| {
141 group
142 .preferred_version
143 .unwrap_or_else(|| group.versions.remove(0))
144 })
145 .map(|gv| gv.group_version)
146 .collect::<BTreeSet<_>>();
147 let resources = self
148 .server_api_resources()
149 .await?
150 .into_iter()
151 .filter(|arl| preferred_versions.contains(&arl.group_version))
152 .collect();
153 Ok(resources)
154 }
155
156 pub async fn api_versions(&self) -> kube::Result<()> {
157 self.server_api_groups()
158 .await?
159 .groups
160 .iter()
161 .flat_map(|group| group.versions.iter())
162 .for_each(|version| println!("{}", version.group_version));
163 Ok(())
164 }
165
166 pub fn dynamic_object_api(
167 &self,
168 scope: discovery::Scope,
169 dyntype: &discovery::ApiResource,
170 ) -> kube::Result<api::Api<api::DynamicObject>> {
171 trace!(?scope, ?dyntype, "dynamic_object_api");
172 let client = self.client()?;
173 let dynamic_api = match scope {
174 discovery::Scope::Cluster => api::Api::all_with(client, dyntype),
175 discovery::Scope::Namespaced => match &self.namespace {
176 Namespace::All => api::Api::all_with(client, dyntype),
177 Namespace::Default => api::Api::default_namespaced_with(client, dyntype),
178 Namespace::Namespace(ns) => api::Api::namespaced_with(client, ns, dyntype),
179 },
180 };
181
182 Ok(dynamic_api)
183 }
184
185 pub fn get_params(&self) -> api::GetParams {
212 api::GetParams::default()
213 }
214
215 pub fn list_params(&self) -> api::ListParams {
216 api::ListParams::default()
217 }
218
219 pub fn post_params(&self) -> api::PostParams {
220 api::PostParams::default()
221 }
222
223 pub fn delete_params(&self, cascade: Cascade, dry_run: DryRun) -> api::DeleteParams {
224 let dp = match cascade {
225 Cascade::Background => api::DeleteParams::background(),
226 Cascade::Foreground => api::DeleteParams::foreground(),
227 Cascade::Orphan => api::DeleteParams::orphan(),
228 };
229
230 match dry_run {
231 DryRun::Server => dp.dry_run(),
232 DryRun::None | DryRun::Client => dp,
233 }
234 }
235
236 pub fn post_params_with_manager(&self, manager: &str) -> api::PostParams {
237 api::PostParams {
238 field_manager: Some(manager.to_string()),
239 ..default()
240 }
241 }
242
243 pub fn clusterroles(&self) -> kube::Result<api::Api<rbacv1::ClusterRole>> {
244 self.cluster_api()
245 }
246
247 pub fn namespaces(&self) -> kube::Result<api::Api<corev1::Namespace>> {
248 self.cluster_api()
249 }
250
251 pub fn pods(&self) -> kube::Result<api::Api<corev1::Pod>> {
252 self.namespaced_api()
253 }
254
255 pub fn configmaps(&self) -> kube::Result<api::Api<corev1::ConfigMap>> {
256 self.namespaced_api()
257 }
258
259 pub fn secrets(&self) -> kube::Result<api::Api<corev1::Secret>> {
260 self.namespaced_api()
261 }
262
263 pub fn componentstatuses(&self) -> kube::Result<api::Api<corev1::ComponentStatus>> {
264 self.cluster_api()
265 }
266
267 pub fn nodes(&self) -> kube::Result<api::Api<corev1::Node>> {
268 self.cluster_api()
269 }
270
271 pub fn selfsubjectaccessreviews(
272 &self,
273 ) -> kube::Result<api::Api<authorizationv1::SelfSubjectAccessReview>> {
274 self.cluster_api()
275 }
276
277 pub fn selfsubjectrulesreviews(
278 &self,
279 ) -> kube::Result<api::Api<authorizationv1::SelfSubjectRulesReview>> {
280 self.cluster_api()
281 }
282
283 pub fn selfsubjectreviews(
284 &self,
285 ) -> kube::Result<api::Api<authenticationv1::SelfSubjectReview>> {
286 self.cluster_api()
287 }
288
289 pub fn inspect<K>(&self, k: &K)
290 where
291 K: serde::Serialize,
292 {
293 if self.debug {
294 let k = yaml::to_string(k).unwrap_or_default();
295 println!("{k}");
296 }
297 }
298
299 pub fn inspect_err(&self, err: &kube::Error) {
300 if self.debug {
301 println!("{err:?}");
302 }
303 }
304
305 fn cluster_api<K>(&self) -> kube::Result<api::Api<K>>
306 where
307 K: kube::Resource<Scope = k8s::openapi::ClusterResourceScope>,
308 <K as kube::Resource>::DynamicType: Default,
309 {
310 self.client().map(|client| client.api())
311 }
312
313 fn namespaced_api<K>(&self) -> kube::Result<api::Api<K>>
314 where
315 K: kube::Resource<Scope = k8s::openapi::NamespaceResourceScope>,
316 <K as kube::Resource>::DynamicType: Default,
317 {
318 let client = self.client()?;
319 let api = match &self.namespace {
320 Namespace::All => client.api(),
321 Namespace::Default => client.default_namespaced_api(),
322 Namespace::Namespace(namespace) => client.namespaced_api(namespace),
323 };
324 Ok(api)
325 }
326
327 pub fn full_name<K>(&self, k: &K) -> String
328 where
329 K: kube::Resource + kube::ResourceExt,
330 <K as kube::Resource>::DynamicType: Default,
331 {
332 let kind = K::kind(&default()).to_lowercase();
333 let name = k.name_any();
334 format!("{kind}/{name}")
335 }
336}
337
338impl Kubeapi {
339 pub fn local() -> Self {
340 let config = kube::Config::new("http://localhost:6443".parse().unwrap());
341 Self {
342 config,
343 kubeconfig: default(),
344 cache: default(),
345 namespace: default(),
346 debug: default(),
347 options: default(),
348 }
349 }
350}
351
352fn default<T: Default>() -> T {
353 T::default()
354}