1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use crate::errors::{
Error::{Other, API},
Result,
};
use chrono::{DateTime, Utc};
use hyper::{Body, Method, Request};
use serde::{Deserialize, Serialize};
use tokio::time::Duration;
pub async fn fetch_instance_id() -> Result<String> {
fetch_metadata_by_path("instance-id").await
}
pub async fn fetch_public_hostname() -> Result<String> {
fetch_metadata_by_path("public-hostname").await
}
pub async fn fetch_public_ipv4() -> Result<String> {
fetch_metadata_by_path("public-ipv4").await
}
pub async fn fetch_availability_zone() -> Result<String> {
fetch_metadata_by_path("placement/availability-zone").await
}
pub async fn fetch_spot_instance_action() -> Result<InstanceAction> {
let resp = fetch_metadata_by_path("spot/instance-action").await?;
serde_json::from_slice(resp.as_bytes()).map_err(|e| Other {
message: format!(
"failed to parse spot/instance-action response '{}' {:?}",
resp, e
),
is_retryable: false,
})
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct InstanceAction {
pub action: String,
#[serde(with = "rfc_manager::serde_format::rfc_3339")]
pub time: DateTime<Utc>,
}
pub async fn fetch_region() -> Result<String> {
let mut az = fetch_availability_zone().await?;
az.truncate(az.len() - 1);
Ok(az)
}
pub async fn fetch_metadata_by_path(path: &str) -> Result<String> {
log::info!("fetching meta-data/{}", path);
let uri = format!("http://169.254.169.254/latest/meta-data/{}", path);
let token = fetch_token().await?;
let req = match Request::builder()
.method(Method::GET)
.uri(uri)
.header("X-aws-ec2-metadata-token", token)
.body(Body::empty())
{
Ok(r) => r,
Err(e) => {
return Err(API {
message: format!("failed to build GET meta-data/{} {:?}", path, e),
is_retryable: false,
});
}
};
let ret = http_manager::read_bytes(req, Duration::from_secs(5), false, true).await;
match ret {
Ok(bytes) => match String::from_utf8(bytes.to_vec()) {
Ok(text) => Ok(text),
Err(e) => Err(API {
message: format!(
"GET meta-data/{} returned unexpected bytes {:?} ({})",
path, bytes, e
),
is_retryable: false,
}),
},
Err(e) => Err(API {
message: format!("failed GET meta-data/{} {:?}", path, e),
is_retryable: false,
}),
}
}
const IMDS_V2_SESSION_TOKEN_URI: &str = "http://169.254.169.254/latest/api/token";
async fn fetch_token() -> Result<String> {
log::info!("fetching IMDS v2 token");
let req = match Request::builder()
.method(Method::PUT)
.uri(IMDS_V2_SESSION_TOKEN_URI)
.header("X-aws-ec2-metadata-token-ttl-seconds", "21600")
.body(Body::empty())
{
Ok(r) => r,
Err(e) => {
return Err(API {
message: format!("failed to build PUT api/token {:?}", e),
is_retryable: false,
});
}
};
let ret = http_manager::read_bytes(req, Duration::from_secs(5), false, true).await;
match ret {
Ok(bytes) => match String::from_utf8(bytes.to_vec()) {
Ok(text) => Ok(text),
Err(e) => Err(API {
message: format!(
"PUT api/token returned unexpected bytes {:?} ({})",
bytes, e
),
is_retryable: false,
}),
},
Err(e) => Err(API {
message: format!("failed PUT api/token {:?}", e),
is_retryable: false,
}),
}
}