use anyhow::{anyhow, Result};
use rusoto_core::Region;
use rusoto_ec2::{DescribeInstancesRequest, Ec2, Ec2Client, Tag};
pub(crate) const TEMPLATE_KEY: &str = "awsec2tag";
pub struct AwsEc2TagLoader {
tags: Vec<Tag>,
}
impl AwsEc2TagLoader {
pub async fn new() -> Result<Self> {
let region: Region = crate::loader::awsec2metadata::get_current_region()
.await?
.parse()
.unwrap_or_default();
let client = Ec2Client::new(region);
Self::with_client(client).await
}
pub async fn with_client(client: Ec2Client) -> Result<Self> {
Self::with_client_and_metadata_url(client, crate::loader::awsec2metadata::METADATA_BASE_URL)
.await
}
pub async fn with_client_and_metadata_url(
client: Ec2Client,
metadata_url: &str,
) -> Result<Self> {
let instance_id =
crate::loader::awsec2metadata::get_metadata_value(metadata_url, "instance-id").await?;
let req = DescribeInstancesRequest {
instance_ids: Some(vec![instance_id]),
..Default::default()
};
let response = match client.describe_instances(req).await {
Ok(response) => response,
Err(e) => return Err(anyhow!("Failed to fetch tag value: {}", e)),
};
let tags = response
.reservations
.ok_or_else(|| anyhow!("Reservations missing from response"))?
.first()
.ok_or_else(|| anyhow!("No Reservations found"))?
.instances
.as_ref()
.ok_or_else(|| anyhow!("Instances missing from response"))?
.first()
.ok_or_else(|| anyhow!("No Instances found"))?
.tags
.as_ref()
.ok_or_else(|| anyhow!("Tags missing from response"))?
.clone();
Ok(Self { tags })
}
async fn get_tag_value(&self, key: &str) -> Result<String> {
let value = self
.tags
.iter()
.filter(|t| t.key.as_ref().unwrap_or(&"".into()).to_lowercase() == key.to_lowercase())
.collect::<Vec<&rusoto_ec2::Tag>>()
.first()
.ok_or_else(|| anyhow!("Tag with key '{}' not found", key))?
.value
.as_ref()
.ok_or_else(|| anyhow!("Tag has no value"))?
.clone();
Ok(value)
}
}
#[async_trait::async_trait]
impl crate::Loader for AwsEc2TagLoader {
async fn load(&self, key: &str) -> Result<String> {
self.get_tag_value(key).await
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Loader;
use rusoto_mock::{
MockCredentialsProvider, MockRequestDispatcher, MockResponseReader, ReadMockResponse,
};
fn tag_value() -> String {
String::from("test value")
}
#[tokio::test]
async fn test_aws_ec2_tag_load_basic() {
let mock_client = rusoto_ec2::Ec2Client::new_with(
MockRequestDispatcher::default().with_body(&MockResponseReader::read_response(
"testdata/awsec2tag",
"describe-instances-response.xml",
)),
MockCredentialsProvider,
Default::default(),
);
let m = mockito::mock("GET", "/instance-id")
.with_status(200)
.with_body("i-01234567890123456")
.expect(1)
.create();
let url = &mockito::server_url();
let loader = AwsEc2TagLoader::with_client_and_metadata_url(mock_client, url)
.await
.unwrap();
let actual = loader.load("TestTag").await.unwrap();
m.assert();
assert_eq!(tag_value(), actual);
}
#[tokio::test]
async fn test_aws_ec2_tag_load_is_case_insensitive() {
let mock_client = rusoto_ec2::Ec2Client::new_with(
MockRequestDispatcher::default().with_body(&MockResponseReader::read_response(
"testdata/awsec2tag",
"describe-instances-response.xml",
)),
MockCredentialsProvider,
Default::default(),
);
let m = mockito::mock("GET", "/instance-id")
.with_status(200)
.with_body("i-01234567890123456")
.expect(1)
.create();
let url = &mockito::server_url();
let loader = AwsEc2TagLoader::with_client_and_metadata_url(mock_client, url)
.await
.unwrap();
let actual = loader.load("testtag").await.unwrap();
m.assert();
assert_eq!(tag_value(), actual);
}
#[tokio::test]
async fn test_aws_ec2_tag_load_caches_tags() {
let mock_client = rusoto_ec2::Ec2Client::new_with(
MockRequestDispatcher::default().with_body(&MockResponseReader::read_response(
"testdata/awsec2tag",
"describe-instances-response.xml",
)),
MockCredentialsProvider,
Default::default(),
);
let m = mockito::mock("GET", "/instance-id")
.with_status(200)
.with_body("i-01234567890123456")
.expect(1)
.create();
let url = &mockito::server_url();
let loader = AwsEc2TagLoader::with_client_and_metadata_url(mock_client, url)
.await
.unwrap();
assert_eq!(tag_value(), loader.load("TestTag").await.unwrap());
assert_eq!(tag_value(), loader.load("TestTag").await.unwrap());
assert_eq!(tag_value(), loader.load("TestTag").await.unwrap());
assert_eq!(
String::from("my-instance"),
loader.load("Name").await.unwrap()
);
m.assert();
}
}