use anyhow::Result;
use super::credentials::{load_credentials, load_credentials_with_sso_check, CredentialsError};
use super::http::AwsHttpClient;
pub enum ClientResult {
Ok(AwsClients, String),
SsoLoginRequired {
profile: String,
sso_session: String,
region: String,
endpoint_url: Option<String>,
},
ConsoleLoginRequired {
profile: String,
login_session: String,
region: String,
endpoint_url: Option<String>,
},
}
pub struct AwsClients {
pub http: AwsHttpClient,
pub region: String,
pub profile: String,
}
impl AwsClients {
pub fn dummy() -> Self {
let creds = crate::aws::credentials::Credentials {
access_key_id: "demo".into(),
secret_access_key: "demo".into(),
session_token: None,
};
Self {
http: AwsHttpClient::new(creds, "eu-west-1", None),
region: "eu-west-1".into(),
profile: "demo".into(),
}
}
pub async fn new(
profile: &str,
region: &str,
endpoint_url: Option<String>,
) -> Result<(Self, String)> {
let profile_str = profile.to_string();
let region_str = region.to_string();
let profile_for_closure = profile_str.clone();
let credentials =
tokio::task::spawn_blocking(move || load_credentials(&profile_for_closure)).await??;
let http = AwsHttpClient::new(credentials, ®ion_str, endpoint_url);
let client = Self {
http,
region: region_str.clone(),
profile: profile_str,
};
Ok((client, region_str))
}
pub async fn new_with_sso_check(
profile: &str,
region: &str,
endpoint_url: Option<String>,
) -> Result<ClientResult> {
let profile = profile.to_string();
let region = region.to_string();
let endpoint = endpoint_url.clone();
let cred_result = tokio::task::spawn_blocking(move || {
load_credentials_with_sso_check(&profile).map(|c| (c, profile))
})
.await?;
match cred_result {
Ok((credentials, prof)) => {
let http = AwsHttpClient::new(credentials, ®ion, endpoint_url);
let client = Self {
http,
region: region.clone(),
profile: prof,
};
Ok(ClientResult::Ok(client, region))
}
Err(CredentialsError::SsoLoginRequired {
profile,
sso_session,
}) => Ok(ClientResult::SsoLoginRequired {
profile,
sso_session,
region,
endpoint_url: endpoint,
}),
Err(CredentialsError::ConsoleLoginRequired {
profile,
login_session,
}) => Ok(ClientResult::ConsoleLoginRequired {
profile,
login_session,
region,
endpoint_url: endpoint,
}),
Err(CredentialsError::Other(e)) => Err(e),
}
}
pub async fn switch_region(&mut self, profile: &str, region: &str) -> Result<String> {
let profile_str = profile.to_string();
let region_str = region.to_string();
let profile_for_closure = profile_str.clone();
let credentials =
tokio::task::spawn_blocking(move || load_credentials(&profile_for_closure)).await??;
self.http.set_credentials(credentials);
self.http.set_region(®ion_str);
self.region = region_str.clone();
self.profile = profile_str;
Ok(region_str)
}
}
pub fn format_aws_error(err: &anyhow::Error) -> String {
let err_str = err.to_string();
if err_str.contains("dispatch failure") || err_str.contains("connection") {
return "Connection failed - check internet/credentials".to_string();
}
if err_str.contains("InvalidClientTokenId") || err_str.contains("SignatureDoesNotMatch") {
return "Invalid credentials - run 'aws configure'".to_string();
}
if err_str.contains("ExpiredToken") {
return "Credentials expired - refresh or reconfigure".to_string();
}
if err_str.contains("AccessDenied") || err_str.contains("UnauthorizedAccess") {
return "Access denied - check IAM permissions".to_string();
}
if err_str.contains("No credentials") || err_str.contains("no credentials") {
return "No credentials - run 'aws configure'".to_string();
}
if err_str.contains("timeout") || err_str.contains("Timeout") {
return "Request timed out - check connection".to_string();
}
if err_str.contains("not available yet") {
return err_str;
}
if err_str.contains("region") {
return "Region error - check AWS_REGION".to_string();
}
if err_str.len() > 60 {
format!("{}...", &err_str[..60])
} else {
err_str
}
}