aws_manager/ec2/
metadata.rs1use crate::errors::{Error, Result};
2use chrono::{DateTime, Utc};
3use reqwest::ClientBuilder;
4use serde::{Deserialize, Serialize};
5use tokio::time::Duration;
6
7pub async fn fetch_instance_id() -> Result<String> {
10 fetch_metadata_by_path("instance-id").await
11}
12
13pub async fn fetch_public_hostname() -> Result<String> {
16 fetch_metadata_by_path("public-hostname").await
17}
18
19pub async fn fetch_public_ipv4() -> Result<String> {
22 fetch_metadata_by_path("public-ipv4").await
23}
24
25pub async fn fetch_availability_zone() -> Result<String> {
28 fetch_metadata_by_path("placement/availability-zone").await
29}
30
31pub async fn fetch_spot_instance_action() -> Result<InstanceAction> {
39 let resp = fetch_metadata_by_path("spot/instance-action").await?;
40 serde_json::from_slice(resp.as_bytes()).map_err(|e| Error::Other {
41 message: format!(
42 "failed to parse spot/instance-action response '{}' {:?}",
43 resp, e
44 ),
45 retryable: false,
46 })
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
52#[serde(rename_all = "snake_case")]
53pub struct InstanceAction {
54 pub action: String,
55 #[serde(with = "rfc_manager::serde_format::rfc_3339")]
56 pub time: DateTime<Utc>,
57}
58
59pub async fn fetch_region() -> Result<String> {
63 let mut az = fetch_availability_zone().await?;
64 az.truncate(az.len() - 1);
65 Ok(az)
66}
67
68pub async fn fetch_metadata_by_path(path: &str) -> Result<String> {
74 log::info!("fetching meta-data/{}", path);
75
76 let token = fetch_token().await?;
77
78 let uri = format!("http://169.254.169.254/latest/meta-data/{}", path);
79 let cli = ClientBuilder::new()
80 .user_agent(env!("CARGO_PKG_NAME"))
81 .danger_accept_invalid_certs(true)
82 .timeout(Duration::from_secs(15))
83 .connection_verbose(true)
84 .build()
85 .map_err(|e| Error::API {
86 message: format!("failed ClientBuilder build {:?}", e),
87 retryable: false,
88 })?;
89 let resp = cli
90 .get(&uri)
91 .header("X-aws-ec2-metadata-token", token)
92 .send()
93 .await
94 .map_err(|e| Error::API {
95 message: format!("failed to build GET meta-data/{} {:?}", path, e),
96 retryable: false,
97 })?;
98 let out = resp.bytes().await.map_err(|e| Error::API {
99 message: format!("failed to read bytes {:?}", e),
100 retryable: false,
101 })?;
102 let out: Vec<u8> = out.into();
103
104 match String::from_utf8(out) {
105 Ok(text) => Ok(text),
106 Err(e) => Err(Error::API {
107 message: format!("GET meta-data/{} failed String::from_utf8 ({})", path, e),
108 retryable: false,
109 }),
110 }
111}
112
113const IMDS_V2_SESSION_TOKEN_URI: &str = "http://169.254.169.254/latest/api/token";
118
119async fn fetch_token() -> Result<String> {
121 log::info!("fetching IMDS v2 token");
122
123 let cli = ClientBuilder::new()
124 .user_agent(env!("CARGO_PKG_NAME"))
125 .danger_accept_invalid_certs(true)
126 .timeout(Duration::from_secs(15))
127 .connection_verbose(true)
128 .build()
129 .map_err(|e| Error::API {
130 message: format!("failed ClientBuilder build {:?}", e),
131 retryable: false,
132 })?;
133 let resp = cli
134 .put(IMDS_V2_SESSION_TOKEN_URI)
135 .header("X-aws-ec2-metadata-token-ttl-seconds", "21600")
136 .send()
137 .await
138 .map_err(|e| Error::API {
139 message: format!("failed to build PUT api/token {:?}", e),
140 retryable: false,
141 })?;
142 let out = resp.bytes().await.map_err(|e| Error::API {
143 message: format!("failed to read bytes {:?}", e),
144 retryable: false,
145 })?;
146 let out: Vec<u8> = out.into();
147
148 match String::from_utf8(out) {
149 Ok(text) => Ok(text),
150 Err(e) => Err(Error::API {
151 message: format!("GET token failed String::from_utf8 ({})", e),
152 retryable: false,
153 }),
154 }
155}