1use crate::config::AwsConfig;
2use anyhow::Result;
3
4#[derive(Clone, Debug)]
5pub struct LambdaFunction {
6 pub name: String,
7 pub arn: String,
8 pub application: Option<String>,
9 pub description: String,
10 pub package_type: String,
11 pub runtime: String,
12 pub architecture: String,
13 pub code_size: i64,
14 pub code_sha256: String,
15 pub memory_mb: i32,
16 pub timeout_seconds: i32,
17 pub last_modified: String,
18 pub layers: Vec<LambdaLayer>,
19}
20
21#[derive(Clone, Debug)]
22pub struct LambdaLayer {
23 pub arn: String,
24 pub code_size: i64,
25}
26
27#[derive(Clone, Debug)]
28pub struct LambdaVersion {
29 pub version: String,
30 pub aliases: String,
31 pub description: String,
32 pub last_modified: String,
33 pub architecture: String,
34}
35
36#[derive(Clone, Debug)]
37pub struct LambdaAlias {
38 pub name: String,
39 pub versions: String,
40 pub description: String,
41}
42
43pub struct LambdaClient {
44 config: AwsConfig,
45}
46
47impl LambdaClient {
48 pub fn new(config: AwsConfig) -> Self {
49 Self { config }
50 }
51
52 pub async fn list_functions(&self) -> Result<Vec<LambdaFunction>> {
53 let client = self.config.lambda_client().await;
54
55 let mut functions = Vec::new();
56 let mut next_marker: Option<String> = None;
57
58 loop {
59 let mut request = client.list_functions().max_items(100);
60 if let Some(marker) = next_marker {
61 request = request.marker(marker);
62 }
63
64 let response = request.send().await?;
65
66 if let Some(funcs) = response.functions {
67 for func in funcs {
68 let last_modified = func
70 .last_modified
71 .as_deref()
72 .map(|s| {
73 if let Ok(dt) =
75 chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.3f%z")
76 {
77 dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
78 } else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
79 dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
80 } else {
81 s.to_string()
82 }
83 })
84 .unwrap_or_default();
85
86 let function_name = func.function_name.unwrap_or_default();
87
88 let application = function_name
91 .rsplit_once('-')
92 .map(|(prefix, _)| prefix.to_string());
93
94 let layers = func
95 .layers
96 .unwrap_or_default()
97 .into_iter()
98 .map(|layer| LambdaLayer {
99 arn: layer.arn.unwrap_or_default(),
100 code_size: layer.code_size,
101 })
102 .collect();
103
104 functions.push(LambdaFunction {
105 name: function_name,
106 arn: func.function_arn.unwrap_or_default(),
107 application,
108 description: func.description.unwrap_or_default(),
109 package_type: func
110 .package_type
111 .map(|p| format!("{:?}", p))
112 .unwrap_or_default(),
113 runtime: func.runtime.map(|r| format!("{:?}", r)).unwrap_or_default(),
114 architecture: func
115 .architectures
116 .and_then(|a| a.first().map(|arch| format!("{:?}", arch)))
117 .unwrap_or_default(),
118 code_size: func.code_size,
119 code_sha256: func.code_sha256.unwrap_or_default(),
120 memory_mb: func.memory_size.unwrap_or(0),
121 timeout_seconds: func.timeout.unwrap_or(0),
122 last_modified,
123 layers,
124 });
125 }
126 }
127
128 next_marker = response.next_marker;
129 if next_marker.is_none() {
130 break;
131 }
132 }
133
134 Ok(functions)
135 }
136
137 pub async fn list_applications(&self) -> Result<Vec<LambdaApplication>> {
138 let client = self.config.cloudformation_client().await;
139
140 let mut applications = Vec::new();
141 let mut next_token: Option<String> = None;
142
143 loop {
144 let mut request = client.list_stacks();
145 if let Some(token) = next_token {
146 request = request.next_token(token);
147 }
148
149 let response = request.send().await?;
150
151 if let Some(stacks) = response.stack_summaries {
152 for stack in stacks {
153 let status = stack
155 .stack_status
156 .map(|s| format!("{:?}", s))
157 .unwrap_or_default();
158 if status.contains("DELETE") {
159 continue;
160 }
161
162 applications.push(LambdaApplication {
163 name: stack.stack_name.unwrap_or_default(),
164 arn: stack.stack_id.unwrap_or_default(),
165 description: stack.template_description.unwrap_or_default(),
166 status,
167 last_modified: stack
168 .last_updated_time
169 .or(stack.creation_time)
170 .map(|dt| {
171 let timestamp = dt.secs();
172 let datetime = chrono::DateTime::from_timestamp(timestamp, 0)
173 .unwrap_or_default();
174 datetime.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
175 })
176 .unwrap_or_default(),
177 });
178 }
179 }
180
181 next_token = response.next_token;
182 if next_token.is_none() {
183 break;
184 }
185 }
186
187 Ok(applications)
188 }
189
190 pub async fn list_versions(&self, function_name: &str) -> Result<Vec<LambdaVersion>> {
191 let client = self.config.lambda_client().await;
192
193 let mut versions = Vec::new();
194 let mut next_marker: Option<String> = None;
195
196 loop {
197 let mut request = client
198 .list_versions_by_function()
199 .function_name(function_name)
200 .max_items(100);
201 if let Some(marker) = next_marker {
202 request = request.marker(marker);
203 }
204
205 let response = request.send().await?;
206
207 if let Some(vers) = response.versions {
208 for ver in vers {
209 let last_modified = ver
210 .last_modified
211 .as_deref()
212 .map(|s| {
213 if let Ok(dt) =
214 chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.3f%z")
215 {
216 dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
217 } else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
218 dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
219 } else {
220 s.to_string()
221 }
222 })
223 .unwrap_or_default();
224
225 versions.push(LambdaVersion {
226 version: ver.version.unwrap_or_default(),
227 aliases: String::new(), description: ver.description.unwrap_or_default(),
229 last_modified,
230 architecture: ver
231 .architectures
232 .and_then(|a| a.first().map(|arch| format!("{:?}", arch)))
233 .unwrap_or_default(),
234 });
235 }
236 }
237
238 next_marker = response.next_marker;
239 if next_marker.is_none() {
240 break;
241 }
242 }
243
244 let aliases_response = client
246 .list_aliases()
247 .function_name(function_name)
248 .send()
249 .await?;
250
251 if let Some(aliases) = aliases_response.aliases {
252 for alias in aliases {
253 let alias_name = alias.name.unwrap_or_default();
254
255 if let Some(version) = alias.function_version {
257 if let Some(ver) = versions.iter_mut().find(|v| v.version == version) {
258 if !ver.aliases.is_empty() {
259 ver.aliases.push_str(", ");
260 }
261 ver.aliases.push_str(&alias_name);
262 }
263 }
264
265 if let Some(routing_config) = alias.routing_config {
267 if let Some(additional_version_weights) =
268 routing_config.additional_version_weights
269 {
270 for (version, _weight) in additional_version_weights {
271 if let Some(ver) = versions.iter_mut().find(|v| v.version == version) {
272 if !ver.aliases.is_empty() {
273 ver.aliases.push_str(", ");
274 }
275 ver.aliases.push_str(&alias_name);
276 }
277 }
278 }
279 }
280 }
281 }
282
283 Ok(versions)
284 }
285}
286
287#[derive(Clone, Debug)]
288pub struct LambdaApplication {
289 pub name: String,
290 pub arn: String,
291 pub description: String,
292 pub status: String,
293 pub last_modified: String,
294}
295
296impl LambdaClient {
297 pub async fn list_aliases(&self, function_name: &str) -> Result<Vec<LambdaAlias>> {
298 let client = self.config.lambda_client().await;
299 let response = client
300 .list_aliases()
301 .function_name(function_name)
302 .send()
303 .await?;
304
305 let mut aliases = Vec::new();
306 if let Some(alias_list) = response.aliases {
307 for alias in alias_list {
308 let primary_version = alias.function_version.unwrap_or_default();
309
310 let mut versions_str = primary_version.clone();
312 if let Some(routing_config) = alias.routing_config {
313 if let Some(additional_version_weights) =
314 routing_config.additional_version_weights
315 {
316 for (version, weight) in additional_version_weights {
317 versions_str.push_str(&format!(
318 ", {} ({}%)",
319 version,
320 (weight * 100.0) as i32
321 ));
322 }
323 }
324 }
325
326 aliases.push(LambdaAlias {
327 name: alias.name.unwrap_or_default(),
328 versions: versions_str,
329 description: alias.description.unwrap_or_default(),
330 });
331 }
332 }
333
334 Ok(aliases)
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 #[test]
341 fn test_application_extraction_from_function_name() {
342 let name = "storefront-studio-beta-api";
344 let application = name.rsplit_once('-').map(|(prefix, _)| prefix).unwrap();
345 assert_eq!(application, "storefront-studio-beta");
346
347 let name = "myapp-api";
349 let application = name.rsplit_once('-').map(|(prefix, _)| prefix).unwrap();
350 assert_eq!(application, "myapp");
351
352 let name = "simplefunction";
354 let application = name.rsplit_once('-').map(|(prefix, _)| prefix);
355 assert_eq!(application, None);
356
357 let name = "my-complex-app-name-worker";
359 let application = name.rsplit_once('-').map(|(prefix, _)| prefix).unwrap();
360 assert_eq!(application, "my-complex-app-name");
361 }
362}