Skip to main content

kube_client/config/
file_loader.rs

1#[cfg(any(feature = "http-proxy", feature = "socks5"))]
2use crate::config::no_proxy::NoProxy;
3
4use crate::{
5    config::{
6        KubeconfigError,
7        file_config::{AuthInfo, Cluster, Context, Kubeconfig},
8    },
9    util::nonempty,
10};
11
12/// KubeConfigOptions stores options used when loading kubeconfig file.
13#[derive(Default, Clone)]
14pub struct KubeConfigOptions {
15    /// The named context to load
16    pub context: Option<String>,
17    /// The cluster to load
18    pub cluster: Option<String>,
19    /// The user to load
20    pub user: Option<String>,
21}
22
23/// ConfigLoader loads current context, cluster, and authentication information
24/// from a kubeconfig file.
25#[derive(Clone, Debug)]
26pub struct ConfigLoader {
27    pub current_context: Context,
28    pub cluster: Cluster,
29    pub user: AuthInfo,
30}
31
32impl ConfigLoader {
33    /// Returns a config loader based on the cluster information from the kubeconfig file.
34    pub async fn new_from_options(options: &KubeConfigOptions) -> Result<Self, KubeconfigError> {
35        let config = Kubeconfig::read()?;
36        let loader = Self::load(
37            config,
38            options.context.as_ref(),
39            options.cluster.as_ref(),
40            options.user.as_ref(),
41        )
42        .await?;
43
44        Ok(loader)
45    }
46
47    pub async fn new_from_kubeconfig(
48        config: Kubeconfig,
49        options: &KubeConfigOptions,
50    ) -> Result<Self, KubeconfigError> {
51        let loader = Self::load(
52            config,
53            options.context.as_ref(),
54            options.cluster.as_ref(),
55            options.user.as_ref(),
56        )
57        .await?;
58
59        Ok(loader)
60    }
61
62    fn load_from_kubeconfig(
63        config: Kubeconfig,
64        context: Option<&String>,
65        cluster: Option<&String>,
66        user: Option<&String>,
67    ) -> Result<Self, KubeconfigError> {
68        let context_name = if let Some(name) = context {
69            name
70        } else if let Some(name) = &config.current_context {
71            name
72        } else {
73            return Err(KubeconfigError::CurrentContextNotSet);
74        };
75
76        let current_context = config
77            .contexts
78            .iter()
79            .find(|named_context| &named_context.name == context_name)
80            .and_then(|named_context| named_context.context.clone())
81            .ok_or_else(|| KubeconfigError::LoadContext(context_name.clone()))?;
82
83        let cluster_name = cluster.unwrap_or(&current_context.cluster);
84        let cluster = config
85            .clusters
86            .iter()
87            .find(|named_cluster| &named_cluster.name == cluster_name)
88            .and_then(|named_cluster| named_cluster.cluster.clone())
89            .ok_or_else(|| KubeconfigError::LoadClusterOfContext(cluster_name.clone()))?;
90
91        let user_name = user.or_else(|| current_context.user.as_ref());
92
93        // client-go doesn't fail on empty/missing user, so we don't either
94        // see https://github.com/kube-rs/kube/issues/1594
95        let mut auth_info = if let Some(user) = user_name {
96            config
97                .auth_infos
98                .iter()
99                .find(|named_user| &named_user.name == user)
100                .and_then(|named_user| named_user.auth_info.clone())
101                .unwrap_or_else(AuthInfo::default)
102        } else {
103            AuthInfo::default()
104        };
105
106        if let Some(exec_config) = &mut auth_info.exec
107            && exec_config.provide_cluster_info
108        {
109            exec_config.cluster = Some((&cluster).try_into()?);
110        }
111
112        Ok(ConfigLoader {
113            current_context,
114            cluster,
115            user: auth_info,
116        })
117    }
118
119    pub async fn load(
120        config: Kubeconfig,
121        context: Option<&String>,
122        cluster: Option<&String>,
123        user: Option<&String>,
124    ) -> Result<Self, KubeconfigError> {
125        Self::load_from_kubeconfig(config, context, cluster, user)
126    }
127
128    pub fn ca_bundle(&self) -> Result<Option<Vec<Vec<u8>>>, KubeconfigError> {
129        if let Some(bundle) = self.cluster.load_certificate_authority()? {
130            Ok(Some(
131                super::certs(&bundle).map_err(KubeconfigError::ParseCertificates)?,
132            ))
133        } else {
134            Ok(None)
135        }
136    }
137
138    pub fn proxy_url(&self) -> Result<Option<http::Uri>, KubeconfigError> {
139        if let Some(proxy) = nonempty(self.cluster.proxy_url.clone())
140            .or_else(|| nonempty(std::env::var("HTTPS_PROXY").ok()))
141            .or_else(|| nonempty(std::env::var("https_proxy").ok()))
142        {
143            let uri = proxy
144                .parse::<http::Uri>()
145                .map_err(KubeconfigError::ParseProxyUrl)?;
146
147            #[cfg(any(feature = "http-proxy", feature = "socks5"))]
148            {
149                let no_proxy = NoProxy::from_env().map_err(KubeconfigError::ParseNoProxy)?;
150
151                match no_proxy {
152                    Some(no_proxy) if no_proxy.matches(&uri).map_err(KubeconfigError::ParseNoProxy)? => {
153                        Ok(None)
154                    }
155                    _ => Ok(Some(uri)),
156                }
157            }
158
159            #[cfg(not(any(feature = "http-proxy", feature = "socks5")))]
160            Ok(Some(uri))
161        } else {
162            Ok(None)
163        }
164    }
165}
166
167impl TryFrom<Kubeconfig> for ConfigLoader {
168    type Error = KubeconfigError;
169
170    fn try_from(kubeconfig: Kubeconfig) -> Result<Self, KubeconfigError> {
171        Self::load_from_kubeconfig(kubeconfig, None, None, None)
172    }
173}