aws_config/imds/
region.rs1use crate::imds::{self, Client};
12use crate::meta::region::{future, ProvideRegion};
13use crate::provider_config::ProviderConfig;
14use aws_smithy_types::error::display::DisplayErrorContext;
15use aws_types::os_shim_internal::Env;
16use aws_types::region::Region;
17use std::fmt::Debug;
18use tracing::Instrument;
19
20pub struct ImdsRegionProvider {
24 client: Client,
25 env: Env,
26}
27
28impl Debug for ImdsRegionProvider {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("ImdsRegionProvider")
31 .field("client", &"IMDS client truncated for readability")
32 .field("env", &self.env)
33 .finish()
34 }
35}
36
37const REGION_PATH: &str = "/latest/meta-data/placement/region";
38
39impl ImdsRegionProvider {
40 pub fn builder() -> Builder {
42 Builder::default()
43 }
44
45 fn imds_disabled(&self) -> bool {
46 match self.env.get(super::env::EC2_METADATA_DISABLED) {
47 Ok(value) => value.eq_ignore_ascii_case("true"),
48 _ => false,
49 }
50 }
51
52 pub async fn region(&self) -> Option<Region> {
56 if self.imds_disabled() {
57 tracing::debug!("not using IMDS to load region, IMDS is disabled");
58 return None;
59 }
60 match self.client.get(REGION_PATH).await {
61 Ok(region) => {
62 tracing::debug!(region = %region.as_ref(), "loaded region from IMDS");
63 Some(Region::new(String::from(region)))
64 }
65 Err(err) => {
66 tracing::warn!(err = %DisplayErrorContext(&err), "failed to load region from IMDS");
67 None
68 }
69 }
70 }
71}
72
73impl ProvideRegion for ImdsRegionProvider {
74 fn region(&self) -> future::ProvideRegion<'_> {
75 future::ProvideRegion::new(
76 self.region()
77 .instrument(tracing::debug_span!("imds_load_region")),
78 )
79 }
80}
81
82#[derive(Debug, Default)]
84pub struct Builder {
85 provider_config: Option<ProviderConfig>,
86 imds_client_override: Option<imds::Client>,
87}
88
89impl Builder {
90 pub fn configure(self, provider_config: &ProviderConfig) -> Self {
92 Self {
93 provider_config: Some(provider_config.clone()),
94 ..self
95 }
96 }
97
98 pub fn imds_client(mut self, imds_client: imds::Client) -> Self {
100 self.imds_client_override = Some(imds_client);
101 self
102 }
103
104 pub fn build(self) -> ImdsRegionProvider {
106 let provider_config = self.provider_config.unwrap_or_default();
107 let client = self
108 .imds_client_override
109 .unwrap_or_else(|| imds::Client::builder().configure(&provider_config).build());
110 ImdsRegionProvider {
111 client,
112 env: provider_config.env(),
113 }
114 }
115}
116
117#[cfg(test)]
118mod test {
119 use crate::imds::client::test::{imds_request, imds_response, token_request, token_response};
120 use crate::imds::region::ImdsRegionProvider;
121 use crate::provider_config::ProviderConfig;
122 use aws_smithy_async::rt::sleep::TokioSleep;
123 use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient};
124 use aws_smithy_types::body::SdkBody;
125 use aws_types::region::Region;
126 use tracing_test::traced_test;
127
128 #[tokio::test]
129 async fn load_region() {
130 let http_client = StaticReplayClient::new(vec![
131 ReplayEvent::new(
132 token_request("http://169.254.169.254", 21600),
133 token_response(21600, "token"),
134 ),
135 ReplayEvent::new(
136 imds_request(
137 "http://169.254.169.254/latest/meta-data/placement/region",
138 "token",
139 ),
140 imds_response("eu-west-1"),
141 ),
142 ]);
143 let provider = ImdsRegionProvider::builder()
144 .configure(
145 &ProviderConfig::no_configuration()
146 .with_http_client(http_client)
147 .with_sleep_impl(TokioSleep::new()),
148 )
149 .build();
150 assert_eq!(
151 provider.region().await.expect("returns region"),
152 Region::new("eu-west-1")
153 );
154 }
155
156 #[traced_test]
157 #[tokio::test]
158 async fn no_region_imds_disabled() {
159 let http_client = StaticReplayClient::new(vec![ReplayEvent::new(
160 token_request("http://169.254.169.254", 21600),
161 http::Response::builder()
162 .status(403)
163 .body(SdkBody::empty())
164 .unwrap(),
165 )]);
166 let provider = ImdsRegionProvider::builder()
167 .configure(
168 &ProviderConfig::no_configuration()
169 .with_http_client(http_client)
170 .with_sleep_impl(TokioSleep::new()),
171 )
172 .build();
173 assert_eq!(provider.region().await, None);
174 assert!(logs_contain("failed to load region from IMDS"));
175 assert!(logs_contain("IMDS is disabled"));
176 }
177}