use aws_sdk_eks as eks;
use kube_client::Error as KubeError;
use kube_client::config as kubeconfig;
#[expect(async_fn_in_trait)]
pub trait TryEksClusterExt {
async fn try_eks_cluster(
&self,
cluster: impl Into<String>,
) -> Result<eks::types::Cluster, eks::Error>;
async fn try_eks_kube_config(
&self,
cluster: impl Into<String>,
) -> Result<kube_client::Config, KubeError> {
self.try_eks_cluster(cluster)
.await
.map_err(|err| KubeError::Service(Box::new(err)))?
.into_kube_config()
.map_err(KubeError::InferKubeconfig)
}
async fn try_eks_kube_client(
&self,
cluster: impl Into<String>,
) -> Result<kube_client::Client, KubeError> {
let config = self.try_eks_kube_config(cluster).await?;
kube_client::Client::try_from(config)
}
}
impl TryEksClusterExt for eks::Client {
async fn try_eks_cluster(
&self,
cluster: impl Into<String>,
) -> Result<eks::types::Cluster, eks::Error> {
let cluster = cluster.into();
self.describe_cluster()
.name(&cluster)
.send()
.await?
.cluster
.ok_or_else(|| cluster_not_found(&cluster))
}
}
pub trait ToKubeConfig {
fn into_kube_config(self) -> Result<kubeconfig::Config, kubeconfig::KubeconfigError>;
}
impl ToKubeConfig for eks::types::Cluster {
fn into_kube_config(self) -> Result<kubeconfig::Config, kubeconfig::KubeconfigError> {
let client_certificate_data = self.certificate_authority.and_then(|cert| cert.data);
let auth_info = kubeconfig::AuthInfo {
client_certificate_data,
..kubeconfig::AuthInfo::default()
};
let cluster_url = self
.endpoint
.ok_or(kubeconfig::KubeconfigError::MissingClusterUrl)?
.try_into()
.map_err(kubeconfig::KubeconfigError::ParseClusterUrl)?;
let config = kubeconfig::Config {
auth_info,
..kubeconfig::Config::new(cluster_url)
};
Ok(config)
}
}
pub trait IntoKubeconfig {
fn into_kubeconfig(self) -> Result<kubeconfig::Kubeconfig, kubeconfig::KubeconfigError>;
}
impl IntoKubeconfig for eks::types::Cluster {
fn into_kubeconfig(self) -> Result<kubeconfig::Kubeconfig, kubeconfig::KubeconfigError> {
let eks::types::Cluster {
name,
endpoint,
certificate_authority,
..
} = self;
let name = name.unwrap_or_else(|| "eks-cluster".to_string());
let certificate_authority_data = certificate_authority.and_then(|cert| cert.data);
let cluster = kubeconfig::Cluster {
server: endpoint,
insecure_skip_tls_verify: None,
certificate_authority: None,
certificate_authority_data,
proxy_url: None,
disable_compression: None,
tls_server_name: None,
extensions: None,
};
let named_cluster = kubeconfig::NamedCluster {
name: name.clone(),
cluster: Some(cluster),
};
let context = kubeconfig::Context {
cluster: name.clone(),
user: None,
namespace: None,
extensions: None,
};
let named_context = kubeconfig::NamedContext {
name: name.clone(),
context: Some(context),
};
let config = kubeconfig::Kubeconfig {
clusters: vec![named_cluster],
contexts: vec![named_context],
current_context: Some(name),
..kubeconfig::Kubeconfig::default()
};
Ok(config)
}
}
fn cluster_not_found(cluster: &str) -> eks::Error {
let exception = eks::types::error::NotFoundException::builder()
.message(format!("EKS cluster {cluster} not found"))
.build();
eks::Error::NotFoundException(exception)
}
pub async fn default_aws_client() -> eks::Client {
let config = aws_config::load_from_env().await;
eks::Client::new(&config)
}
#[cfg(test)]
mod tests {
use super::{IntoKubeconfig, ToKubeConfig};
use aws_sdk_eks as eks;
use kube_client::config as kubeconfig;
fn make_cluster(
name: Option<&str>,
endpoint: Option<&str>,
cert_data: Option<&str>,
) -> eks::types::Cluster {
let mut builder = eks::types::Cluster::builder();
if let Some(n) = name {
builder = builder.name(n);
}
if let Some(e) = endpoint {
builder = builder.endpoint(e);
}
if let Some(d) = cert_data {
builder =
builder.certificate_authority(eks::types::Certificate::builder().data(d).build());
}
builder.build()
}
#[test]
fn cluster_not_found_error_contains_name() {
let err = super::cluster_not_found("my-cluster");
let eks::Error::NotFoundException(ref ex) = err else {
panic!("expected NotFoundException, got {err:?}");
};
assert!(
ex.message().unwrap_or("").contains("my-cluster"),
"error message should contain the cluster name"
);
}
#[test]
fn to_kube_config_extracts_endpoint_and_cert() {
let cluster = make_cluster(
Some("test"),
Some("https://abc123.gr7.us-east-1.eks.amazonaws.com"),
Some("base64certdata=="),
);
let config = cluster.into_kube_config().expect("should build config");
assert_eq!(
config.cluster_url.host(),
Some("abc123.gr7.us-east-1.eks.amazonaws.com")
);
assert_eq!(
config.auth_info.client_certificate_data.as_deref(),
Some("base64certdata==")
);
}
#[test]
fn to_kube_config_missing_endpoint_returns_error() {
let cluster = make_cluster(Some("test"), None, None);
let err = cluster.into_kube_config().unwrap_err();
assert!(
matches!(err, kubeconfig::KubeconfigError::MissingClusterUrl),
"expected MissingClusterUrl, got {err:?}"
);
}
#[test]
fn into_kubeconfig_uses_cluster_name() {
let cluster = make_cluster(Some("my-cluster"), Some("https://example.k8s.io"), None);
let kc = cluster.into_kubeconfig().expect("should build kubeconfig");
assert_eq!(kc.current_context.as_deref(), Some("my-cluster"));
assert_eq!(kc.clusters[0].name, "my-cluster");
assert_eq!(kc.contexts[0].name, "my-cluster");
assert_eq!(
kc.contexts[0].context.as_ref().map(|c| c.cluster.as_str()),
Some("my-cluster")
);
}
#[test]
fn into_kubeconfig_falls_back_to_eks_cluster_name() {
let cluster = make_cluster(None, Some("https://example.k8s.io"), None);
let kc = cluster.into_kubeconfig().expect("should build kubeconfig");
assert_eq!(kc.current_context.as_deref(), Some("eks-cluster"));
assert_eq!(kc.clusters[0].name, "eks-cluster");
}
#[test]
fn into_kubeconfig_propagates_cert_authority_data() {
let cluster = make_cluster(Some("test"), None, Some("dGVzdA=="));
let kc = cluster.into_kubeconfig().expect("should build kubeconfig");
let cert = kc.clusters[0]
.cluster
.as_ref()
.and_then(|c| c.certificate_authority_data.as_deref());
assert_eq!(cert, Some("dGVzdA=="));
}
}