Skip to main content

mountpoint_s3_client/
imds_crt_client.rs

1//! An Instance Metadata Service (IMDS) client using the AWS Common Runtime implementation.
2
3use futures::channel::oneshot;
4use mountpoint_s3_crt::auth::imds_client::{ImdsClient, ImdsClientConfig};
5use mountpoint_s3_crt::common::allocator::Allocator;
6use mountpoint_s3_crt::common::error;
7use mountpoint_s3_crt::io::channel_bootstrap::{ClientBootstrap, ClientBootstrapOptions};
8use mountpoint_s3_crt::io::event_loop::EventLoopGroup;
9use mountpoint_s3_crt::io::host_resolver::{HostResolver, HostResolverDefaultOptions};
10use serde_json::Value;
11use thiserror::Error;
12
13#[derive(Debug)]
14/// Instance Metadata Service (IMDS) client responsible for sending EC2 instance metadata query requests.
15pub struct ImdsCrtClient {
16    imds_client: ImdsClient,
17    #[allow(unused)]
18    event_loop_group: EventLoopGroup,
19    #[allow(unused)]
20    allocator: Allocator,
21}
22
23impl ImdsCrtClient {
24    pub fn new() -> Result<Self, error::Error> {
25        let allocator = Allocator::default();
26
27        let mut event_loop_group = EventLoopGroup::new_default(&allocator, None, || {}).unwrap();
28
29        let resolver_options = HostResolverDefaultOptions {
30            max_entries: 8,
31            event_loop_group: &mut event_loop_group,
32        };
33
34        let mut host_resolver = HostResolver::new_default(&allocator, &resolver_options).unwrap();
35
36        let bootstrap_options = ClientBootstrapOptions {
37            event_loop_group: &mut event_loop_group,
38            host_resolver: &mut host_resolver,
39        };
40
41        let client_bootstrap = ClientBootstrap::new(&allocator, &bootstrap_options).unwrap();
42
43        let mut client_config = ImdsClientConfig::new();
44        client_config.client_bootstrap(client_bootstrap);
45
46        let imds_client = ImdsClient::new(&allocator, client_config)?;
47
48        Ok(Self {
49            imds_client,
50            event_loop_group,
51            allocator,
52        })
53    }
54
55    /// Query the identity document of the EC2 instance this code is executed on.
56    pub async fn get_identity_document(&self) -> Result<IdentityDocument, ImdsQueryRequestError> {
57        const IDENTITY_DOCUMENT_PATH: &str = "/latest/dynamic/instance-identity/document";
58
59        let (tx, rx) = oneshot::channel();
60        self.imds_client.get_resource(IDENTITY_DOCUMENT_PATH, move |result| {
61            let _ = tx.send(result);
62        })?;
63
64        let json = match rx.await {
65            Ok(Ok(json)) => json,
66            Ok(Err(err)) => return Err(ImdsQueryRequestError::CrtError(err)),
67            Err(err) => return Err(ImdsQueryRequestError::InternalError(Box::new(err))),
68        };
69
70        let json = &json;
71        let parsed: Value =
72            serde_json::from_str(json).map_err(|_| ImdsQueryRequestError::InvalidResponse(json.to_owned()))?;
73        let instance_type = parsed
74            .get("instanceType")
75            .and_then(Value::as_str)
76            .ok_or_else(|| ImdsQueryRequestError::InvalidResponse(json.to_owned()))?
77            .to_owned();
78        let region = parsed
79            .get("region")
80            .and_then(Value::as_str)
81            .ok_or_else(|| ImdsQueryRequestError::InvalidResponse(json.to_owned()))?
82            .to_owned();
83
84        Ok(IdentityDocument { instance_type, region })
85    }
86}
87
88/// Information about an EC2 instance.
89#[derive(Debug)]
90pub struct IdentityDocument {
91    // TODO: Add more fields as required.
92    /// The instance type of the instance.
93    pub instance_type: String,
94    /// The Region in which the instance is running.
95    pub region: String,
96}
97
98/// ImdsQueryRequestError is returned by an asynchronous query.
99#[derive(Error, Debug)]
100pub enum ImdsQueryRequestError {
101    /// An internal error from within the Imds client. The request may have been sent.
102    #[error("Internal Imds client error")]
103    InternalError(#[source] Box<dyn std::error::Error + Send + Sync>),
104
105    /// An internal error from within the AWS Common Runtime. The request may have been sent.
106    #[error("Unknown CRT error")]
107    CrtError(#[from] error::Error),
108
109    /// The response from Imds was not valid.
110    #[error("Invalid response from Imds: {0}")]
111    InvalidResponse(String),
112}