use crate::meta::region::{future, ProvideRegion};
#[allow(deprecated)]
use crate::profile::profile_file::ProfileFiles;
use crate::profile::ProfileSet;
use crate::provider_config::ProviderConfig;
use aws_types::region::Region;
#[doc = include_str!("location_of_profile_files.md")]
#[derive(Debug, Default)]
pub struct ProfileFileRegionProvider {
provider_config: ProviderConfig,
}
#[derive(Debug, Default)]
pub struct Builder {
config: Option<ProviderConfig>,
profile_override: Option<String>,
#[allow(deprecated)]
profile_files: Option<ProfileFiles>,
}
impl Builder {
pub fn configure(mut self, config: &ProviderConfig) -> Self {
self.config = Some(config.clone());
self
}
pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
self.profile_override = Some(profile_name.into());
self
}
#[allow(deprecated)]
pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self {
self.profile_files = Some(profile_files);
self
}
pub fn build(self) -> ProfileFileRegionProvider {
let conf = self
.config
.unwrap_or_default()
.with_profile_config(self.profile_files, self.profile_override);
ProfileFileRegionProvider {
provider_config: conf,
}
}
}
impl ProfileFileRegionProvider {
pub fn new() -> Self {
Self {
provider_config: ProviderConfig::default(),
}
}
pub fn builder() -> Builder {
Builder::default()
}
async fn region(&self) -> Option<Region> {
let profile_set = self.provider_config.profile().await?;
resolve_profile_chain_for_region(profile_set)
}
}
fn resolve_profile_chain_for_region(profile_set: &'_ ProfileSet) -> Option<Region> {
if profile_set.is_empty() {
return None;
}
let mut selected_profile = profile_set.selected_profile();
let mut visited_profiles = vec![];
loop {
let profile = profile_set.get_profile(selected_profile)?;
if visited_profiles.contains(&selected_profile) {
return None;
} else {
visited_profiles.push(selected_profile);
}
let selected_profile_region = profile
.get("region")
.map(|region| Region::new(region.to_owned()));
let source_profile = profile.get("source_profile");
match (selected_profile_region, source_profile) {
(Some(region), _) => {
return Some(region);
}
(None, Some(source_profile)) if source_profile == selected_profile => {
return None;
}
(None, None) => {
return None;
}
(None, Some(source_profile)) => {
selected_profile = source_profile;
}
}
}
}
impl ProvideRegion for ProfileFileRegionProvider {
fn region(&self) -> future::ProvideRegion<'_> {
future::ProvideRegion::new(self.region())
}
}
#[cfg(test)]
mod test {
use crate::profile::ProfileFileRegionProvider;
use crate::provider_config::ProviderConfig;
use crate::test_case::no_traffic_client;
use aws_types::os_shim_internal::{Env, Fs};
use aws_types::region::Region;
use futures_util::FutureExt;
use tracing_test::traced_test;
fn provider_config(dir_name: &str) -> ProviderConfig {
let fs = Fs::from_test_dir(format!("test-data/profile-provider/{}/fs", dir_name), "/");
let env = Env::from_slice(&[("HOME", "/home")]);
ProviderConfig::empty()
.with_fs(fs)
.with_env(env)
.with_http_client(no_traffic_client())
}
#[traced_test]
#[test]
fn load_region() {
let provider = ProfileFileRegionProvider::builder()
.configure(&provider_config("region_override"))
.build();
assert_eq!(
provider.region().now_or_never().unwrap(),
Some(Region::from_static("us-east-1"))
);
}
#[test]
fn load_region_env_profile_override() {
let conf = provider_config("region_override").with_env(Env::from_slice(&[
("HOME", "/home"),
("AWS_PROFILE", "base"),
]));
let provider = ProfileFileRegionProvider::builder()
.configure(&conf)
.build();
assert_eq!(
provider.region().now_or_never().unwrap(),
Some(Region::from_static("us-east-1"))
);
}
#[test]
fn load_region_nonexistent_profile() {
let conf = provider_config("region_override").with_env(Env::from_slice(&[
("HOME", "/home"),
("AWS_PROFILE", "doesnotexist"),
]));
let provider = ProfileFileRegionProvider::builder()
.configure(&conf)
.build();
assert_eq!(provider.region().now_or_never().unwrap(), None);
}
#[test]
fn load_region_explicit_override() {
let conf = provider_config("region_override");
let provider = ProfileFileRegionProvider::builder()
.configure(&conf)
.profile_name("base")
.build();
assert_eq!(
provider.region().now_or_never().unwrap(),
Some(Region::from_static("us-east-1"))
);
}
#[tokio::test]
async fn load_region_from_source_profile() {
let config = r#"
[profile credentials]
aws_access_key_id = test-access-key-id
aws_secret_access_key = test-secret-access-key
aws_session_token = test-session-token
region = us-east-1
[profile needs-source]
source_profile = credentials
role_arn = arn:aws:iam::123456789012:role/test
"#
.trim();
let fs = Fs::from_slice(&[("test_config", config)]);
let env = Env::from_slice(&[("AWS_CONFIG_FILE", "test_config")]);
let provider_config = ProviderConfig::empty()
.with_fs(fs)
.with_env(env)
.with_http_client(no_traffic_client());
assert_eq!(
Some(Region::new("us-east-1")),
ProfileFileRegionProvider::builder()
.profile_name("needs-source")
.configure(&provider_config)
.build()
.region()
.await
);
}
}