use crate::clients::s3::{S3Client, S3ClientOptions};
use crate::config::Config;
use crate::error::{PrefixloadError, Result};
use configparser::ini::Ini;
use requestty::Question;
use std::fs;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
fn input_credentials() -> Result<(String, String)> {
let access_question = Question::input("access_key")
.message("Enter AWS Access Key ID:")
.build();
let secret_question = Question::password("secret_key")
.message("Enter AWS Secret Access Key:")
.mask('*')
.build();
let access_key = requestty::prompt_one(access_question)?
.as_string()
.map(String::from)
.ok_or_else(|| PrefixloadError::Custom("Failed to parse access key.".to_string()))?;
let secret_key = requestty::prompt_one(secret_question)?
.as_string()
.map(String::from)
.ok_or_else(|| PrefixloadError::Custom("Failed to parse secret key.".to_string()))?;
Ok((access_key, secret_key))
}
async fn credentials_valid(access_key: &str, secret_key: &str) -> Result<()> {
let config: Config = Config::load()?;
let s3_options = S3ClientOptions::default()
.with_access_key(access_key.to_string())
.with_secret_key(secret_key.to_string())
.with_endpoint(config.endpoint.clone());
let s3_client = S3Client::new(s3_options).await?;
s3_client.check_bucket_access(&config.bucket).await?;
Ok(())
}
fn save_credentials_to_file(access_key: &str, secret_key: &str) -> Result<()> {
let home_dir = dirs_next::home_dir()
.ok_or_else(|| PrefixloadError::Custom("Cannot find home directory".to_string()))?;
let aws_dir = home_dir.join(".aws");
fs::create_dir_all(&aws_dir).map_err(|err| {
PrefixloadError::Custom(format!("Cannot create {}: {}", aws_dir.display(), err))
})?;
let credentials_path = aws_dir.join("credentials");
let mut config = Ini::new();
config.set("default", "aws_access_key_id", Some(access_key.to_string()));
config.set(
"default",
"aws_secret_access_key",
Some(secret_key.to_string()),
);
config.write(&credentials_path).map_err(|err| {
PrefixloadError::Custom(format!(
"Cannot write to {}: {}",
credentials_path.display(),
err
))
})?;
#[cfg(unix)]
{
let mut perms = fs::metadata(&credentials_path)?.permissions();
perms.set_mode(0o600);
fs::set_permissions(&credentials_path, perms)?;
}
Ok(())
}
pub async fn run() -> Result<String> {
let (access_key, secret_key) = input_credentials()?;
match credentials_valid(&access_key, &secret_key).await {
Ok(()) => {
save_credentials_to_file(&access_key, &secret_key)?;
Ok("Credentials have been saved successfully!".to_string())
}
Err(err) => Err(PrefixloadError::Custom(format!(
"Credentials not valid: {}",
err
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
use std::fs;
use tempfile::tempdir;
#[test]
#[serial]
fn test_save_credentials_to_file() {
let dir = tempdir().unwrap();
unsafe {
std::env::set_var("HOME", dir.path());
}
let access_key = "test_access_key";
let secret_key = "test_secret_key";
let result = save_credentials_to_file(access_key, secret_key);
assert!(result.is_ok());
let credentials_path = dir.path().join(".aws").join("credentials");
assert!(credentials_path.exists());
let content = fs::read_to_string(&credentials_path).unwrap();
assert!(content.contains(&format!("aws_access_key_id={}", access_key)));
assert!(content.contains(&format!("aws_secret_access_key={}", secret_key)));
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = fs::metadata(&credentials_path).unwrap().permissions();
assert_eq!(perms.mode() & 0o777, 0o600);
}
unsafe {
std::env::remove_var("HOME");
}
}
}