use anyhow::Result;
pub(crate) const TEMPLATE_KEY: &str = "awsec2metadata";
pub(crate) const METADATA_BASE_URL: &str = "http://169.254.169.254/latest/meta-data";
pub struct AwsEc2MetadataLoader<'a> {
metadata_url: &'a str,
}
impl<'a> AwsEc2MetadataLoader<'a> {
pub fn new() -> Self {
Self::with_base_url(METADATA_BASE_URL)
}
pub fn with_base_url(url: &'a str) -> Self {
Self { metadata_url: url }
}
}
pub(crate) async fn get_metadata_value(base_url: &str, path: &str) -> Result<String> {
let mut url = String::from(base_url);
if !url.ends_with('/') {
url.push('/');
}
url.push_str(path.trim_start_matches('/'));
let value = surf::get(url)
.await
.map_err(|e| anyhow::anyhow!("{}", e).context("Failed to load metadata value"))?
.body_string()
.await
.map_err(|e| anyhow::anyhow!("{}", e).context("Failed to decode response body"))?;
Ok(value)
}
pub(crate) async fn get_current_region_from_url(base_url: &str) -> Result<String> {
let r = get_metadata_value(base_url, "placement/availability-zone").await?;
Ok(r.trim_end_matches(char::is_alphabetic).to_string())
}
pub(crate) async fn get_current_region() -> Result<String> {
get_current_region_from_url(METADATA_BASE_URL).await
}
#[async_trait::async_trait]
impl crate::Loader for AwsEc2MetadataLoader<'_> {
async fn load(&self, key: &str) -> Result<String> {
get_metadata_value(self.metadata_url, key).await
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Loader;
use mockito::mock;
#[tokio::test]
async fn test_aws_ec2_metadata_basic() {
let expected = "test-id";
let m = mock("GET", "/instance-id")
.with_status(200)
.with_header("Content-Type", "text/plain")
.with_body(expected)
.create();
let mut url = mockito::server_url();
url.push('/');
let loader = AwsEc2MetadataLoader::with_base_url(&url);
let actual = loader.load("instance-id").await.unwrap();
m.assert();
assert_eq!(expected, actual);
}
#[tokio::test]
async fn test_get_current_region() {
let az = "us-east-1a";
let expected = "us-east-1";
let m = mock("GET", "/placement/availability-zone")
.with_status(200)
.with_header("Content-Type", "text/plain")
.with_body(az)
.create();
let mut url = mockito::server_url();
url.push('/');
let actual = get_current_region_from_url(&url).await.unwrap();
m.assert();
assert_eq!(expected, actual);
}
}