diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1f9bfb296..a758279f6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,7 @@ // These extensions are loaded for all users by default. "extensions": [ - "matklad.rust-analyzer", + "rust-lang.rust-analyzer", "NathanRidley.autotrim", "samverschueren.final-newline", "tamasfe.even-better-toml", diff --git a/kube-client/src/config/incluster_config.rs b/kube-client/src/config/incluster_config.rs index cadbd91ef..a3d373dc9 100644 --- a/kube-client/src/config/incluster_config.rs +++ b/kube-client/src/config/incluster_config.rs @@ -16,6 +16,10 @@ pub enum Error { #[error("failed to read a certificate bundle: {0}")] ReadCertificateBundle(#[source] std::io::Error), + /// Failed to parse cluster port value + #[error("failed to parse cluster port: {0}")] + ParseClusterPort(#[source] std::num::ParseIntError), + /// Failed to parse cluster url #[error("failed to parse cluster url: {0}")] ParseClusterUrl(#[source] http::uri::InvalidUri), @@ -29,6 +33,44 @@ pub fn kube_dns() -> http::Uri { http::Uri::from_static("https://kubernetes.default.svc/") } +pub fn try_kube_from_legacy_env() -> Result, Error> { + let host = match std::env::var("KUBERNETES_SERVICE_HOST") { + Ok(h) => h, + Err(_) => return Ok(None), + }; + let port = match std::env::var("KUBERNETES_SERVICE_PORT") { + Ok(p) => p.parse::().map_err(Error::ParseClusterPort)?, + Err(_) => return Ok(None), + }; + + // Format a host and, if not using 443, a port. + // + // Ensure that IPv6 addresses are properly bracketed. + let uri = match host.parse::() { + Ok(ip) => { + if port == 443 { + if ip.is_ipv6() { + format!("https://[{ip}]") + } else { + format!("https://{ip}") + } + } else { + let addr = std::net::SocketAddr::new(ip, port); + format!("https://{addr}") + } + } + Err(_) => { + if port == 443 { + format!("https://{host}") + } else { + format!("https://{host}:{port}") + } + } + }; + + uri.parse().map_err(Error::ParseClusterUrl) +} + pub fn token_file() -> String { SERVICE_TOKENFILE.to_owned() } diff --git a/kube-client/src/config/mod.rs b/kube-client/src/config/mod.rs index 7372a8a50..f53ed2d3a 100644 --- a/kube-client/src/config/mod.rs +++ b/kube-client/src/config/mod.rs @@ -195,7 +195,7 @@ impl Config { "no local config found, falling back to local in-cluster config" ); - Self::from_cluster_env().map_err(|in_cluster_err| InferConfigError { + Self::load_in_cluster().map_err(|in_cluster_err| InferConfigError { in_cluster: in_cluster_err, kubeconfig: kubeconfig_err, })? @@ -206,13 +206,43 @@ impl Config { Ok(config) } - /// Create configuration from the cluster's environment variables - /// - /// This follows the standard [API Access from a Pod](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) - /// and relies on you having the service account's token mounted, - /// as well as having given the service account rbac access to do what you need. + /// Replaced by [`Self::load_in_cluster`]. + #[deprecated(since = "0.75.0", note = "use Config::load_in_cluster")] pub fn from_cluster_env() -> Result { - let cluster_url = incluster_config::kube_dns(); + Self::load_in_cluster() + } + + /// Load the default in-cluster config. + /// + /// This follows the standard [API Access from a Pod][docs] policy of using + /// `https://kubernetes.default.svc` to connect to the Kubernetes API + /// server. A service account's token must be available in + /// `/var/run/secrets/kubernetes.io/serviceaccount/`. + /// + /// [docs]: https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod + pub fn load_in_cluster() -> Result { + Self::load_in_cluster_with_uri(incluster_config::kube_dns()) + } + + /// Load an in-cluster config use the legacy `KUBERNETES_SERVICE_HOST` and + /// `KUBERNETES_SERVICE_PORT` environment variables. + /// + /// This matches the behavior of [client-go], but it will fallback to using + /// `kubernetes.default.svc` if the environment is not set. + /// + /// A service account's token must be available in + /// `/var/run/secrets/kubernetes.io/serviceaccount/`. + /// + /// This will be deprecated/removed when client-go's behavior changes. + /// + /// [client-go]: https://github.com/kubernetes/kubernetes/blob/67bde9a1023d1805e33d698b28aa6fad991dfb39/staging/src/k8s.io/client-go/rest/config.go#L507-L541 + pub fn load_in_cluster_from_legacy_env() -> Result { + let uri = + incluster_config::try_kube_from_legacy_env()?.unwrap_or_else(|| incluster_config::kube_dns()); + Self::load_in_cluster_with_uri(uri) + } + + fn load_in_cluster_with_uri(cluster_url: http::uri::Uri) -> Result { let default_namespace = incluster_config::load_default_ns()?; let root_cert = incluster_config::load_cert()?; @@ -378,7 +408,6 @@ pub use file_config::{ NamedContext, NamedExtension, Preferences, }; - #[cfg(test)] mod tests { #[cfg(not(feature = "client"))] // want to ensure this works without client features