1use crate::config::AwsConfig;
2use crate::metrics::{MetricQuery, MetricReader};
3use anyhow::Result;
4use aws_sdk_cloudwatch::types::Statistic;
5
6#[derive(Clone, Debug)]
7pub struct SqsQueue {
8 pub name: String,
9 pub url: String,
10 pub queue_type: String,
11 pub created_timestamp: String,
12 pub messages_available: String,
13 pub messages_in_flight: String,
14 pub encryption: String,
15 pub content_based_deduplication: String,
16 pub last_modified_timestamp: String,
17 pub visibility_timeout: String,
18 pub message_retention_period: String,
19 pub maximum_message_size: String,
20 pub delivery_delay: String,
21 pub receive_message_wait_time: String,
22 pub high_throughput_fifo: String,
23 pub deduplication_scope: String,
24 pub fifo_throughput_limit: String,
25 pub dead_letter_queue: String,
26 pub messages_delayed: String,
27 pub redrive_allow_policy: String,
28 pub redrive_policy: String,
29 pub redrive_task_id: String,
30 pub redrive_task_start_time: String,
31 pub redrive_task_status: String,
32 pub redrive_task_percent: String,
33 pub redrive_task_destination: String,
34}
35
36#[derive(Clone, Debug)]
37pub struct LambdaTrigger {
38 pub uuid: String,
39 pub arn: String,
40 pub status: String,
41 pub last_modified: String,
42}
43
44#[derive(Clone, Debug)]
45pub struct EventBridgePipe {
46 pub name: String,
47 pub status: String,
48 pub target: String,
49 pub last_modified: String,
50}
51
52#[derive(Clone, Debug)]
53pub struct QueueTag {
54 pub key: String,
55 pub value: String,
56}
57
58pub struct SqsClient {
59 config: AwsConfig,
60}
61
62impl SqsClient {
63 pub fn new(config: AwsConfig) -> Self {
64 Self { config }
65 }
66
67 pub async fn list_lambda_triggers(&self, queue_arn: &str) -> Result<Vec<LambdaTrigger>> {
68 let lambda_client = self.config.lambda_client();
69
70 let response = lambda_client
71 .list_event_source_mappings()
72 .event_source_arn(queue_arn)
73 .send()
74 .await?;
75
76 let mut triggers = Vec::new();
77 if let Some(mappings) = response.event_source_mappings {
78 for mapping in mappings {
79 triggers.push(LambdaTrigger {
80 uuid: mapping.uuid.unwrap_or_default(),
81 arn: mapping.function_arn.unwrap_or_default(),
82 status: mapping
83 .state
84 .map(|s| s.as_str().to_string())
85 .unwrap_or_else(|| "Unknown".to_string()),
86 last_modified: mapping
87 .last_modified
88 .map(|dt| dt.secs().to_string())
89 .unwrap_or_default(),
90 });
91 }
92 }
93
94 Ok(triggers)
95 }
96
97 pub async fn list_queues(&self, prefix: &str) -> Result<Vec<SqsQueue>> {
98 let client = self.config.sqs_client();
99
100 let mut request = client.list_queues();
101 if !prefix.is_empty() {
102 request = request.queue_name_prefix(prefix);
103 }
104
105 let response = request.send().await?;
106 let mut queues = Vec::new();
107
108 if let Some(urls) = response.queue_urls {
109 for url in urls {
110 let attrs_response = client
111 .get_queue_attributes()
112 .queue_url(&url)
113 .attribute_names(aws_sdk_sqs::types::QueueAttributeName::All)
114 .send()
115 .await?;
116
117 let attrs = attrs_response.attributes.unwrap_or_default();
118
119 let name = url.split('/').next_back().unwrap_or(&url).to_string();
120 let queue_type = if name.ends_with(".fifo") {
121 "FIFO".to_string()
122 } else {
123 "Standard".to_string()
124 };
125
126 queues.push(SqsQueue {
127 name,
128 url: url.clone(),
129 queue_type,
130 created_timestamp: attrs
131 .get(&aws_sdk_sqs::types::QueueAttributeName::CreatedTimestamp)
132 .map(|v| v.to_string())
133 .unwrap_or_default(),
134 messages_available: attrs
135 .get(&aws_sdk_sqs::types::QueueAttributeName::ApproximateNumberOfMessages)
136 .map(|v| v.to_string())
137 .unwrap_or_else(|| "0".to_string()),
138 messages_in_flight: attrs
139 .get(&aws_sdk_sqs::types::QueueAttributeName::ApproximateNumberOfMessagesNotVisible)
140 .map(|v| v.to_string())
141 .unwrap_or_else(|| "0".to_string()),
142 encryption: if let Some(kms_key) = attrs.get(&aws_sdk_sqs::types::QueueAttributeName::KmsMasterKeyId) {
143 if kms_key.is_empty() {
144 "SSE-SQS".to_string()
145 } else {
146 "SSE-KMS".to_string()
147 }
148 } else if attrs.contains_key(&aws_sdk_sqs::types::QueueAttributeName::SqsManagedSseEnabled) {
149 "SSE-SQS".to_string()
150 } else {
151 "-".to_string()
152 },
153 content_based_deduplication: attrs
154 .get(&aws_sdk_sqs::types::QueueAttributeName::ContentBasedDeduplication)
155 .map(|v| if v == "true" { "Enabled" } else { "Disabled" })
156 .unwrap_or("Disabled")
157 .to_string(),
158 last_modified_timestamp: attrs
159 .get(&aws_sdk_sqs::types::QueueAttributeName::LastModifiedTimestamp)
160 .map(|v| v.to_string())
161 .unwrap_or_default(),
162 visibility_timeout: attrs
163 .get(&aws_sdk_sqs::types::QueueAttributeName::VisibilityTimeout)
164 .map(|v| v.to_string())
165 .unwrap_or_default(),
166 message_retention_period: attrs
167 .get(&aws_sdk_sqs::types::QueueAttributeName::MessageRetentionPeriod)
168 .map(|v| v.to_string())
169 .unwrap_or_default(),
170 maximum_message_size: attrs
171 .get(&aws_sdk_sqs::types::QueueAttributeName::MaximumMessageSize)
172 .map(|v| format!("{} bytes", v))
173 .unwrap_or_default(),
174 delivery_delay: attrs
175 .get(&aws_sdk_sqs::types::QueueAttributeName::DelaySeconds)
176 .map(|v| v.to_string())
177 .unwrap_or_default(),
178 receive_message_wait_time: attrs
179 .get(&aws_sdk_sqs::types::QueueAttributeName::ReceiveMessageWaitTimeSeconds)
180 .map(|v| v.to_string())
181 .unwrap_or_default(),
182 high_throughput_fifo: attrs
183 .get(&aws_sdk_sqs::types::QueueAttributeName::FifoThroughputLimit)
184 .map(|v| if v == "perMessageGroupId" { "Disabled" } else { "Enabled" })
185 .unwrap_or("-")
186 .to_string(),
187 deduplication_scope: attrs
188 .get(&aws_sdk_sqs::types::QueueAttributeName::DeduplicationScope)
189 .map(|v| v.to_string())
190 .unwrap_or_else(|| "-".to_string()),
191 fifo_throughput_limit: attrs
192 .get(&aws_sdk_sqs::types::QueueAttributeName::FifoThroughputLimit)
193 .map(|v| v.to_string())
194 .unwrap_or_else(|| "-".to_string()),
195 dead_letter_queue: attrs
196 .get(&aws_sdk_sqs::types::QueueAttributeName::RedrivePolicy)
197 .and_then(|policy| {
198 serde_json::from_str::<serde_json::Value>(policy)
200 .ok()
201 .and_then(|v| v.get("deadLetterTargetArn").and_then(|arn| arn.as_str()).map(|s| {
202 s.split(':').next_back().unwrap_or(s).to_string()
204 }))
205 })
206 .unwrap_or_else(|| "-".to_string()),
207 messages_delayed: attrs
208 .get(&aws_sdk_sqs::types::QueueAttributeName::ApproximateNumberOfMessagesDelayed)
209 .map(|v| v.to_string())
210 .unwrap_or_else(|| "0".to_string()),
211 redrive_allow_policy: attrs
212 .get(&aws_sdk_sqs::types::QueueAttributeName::RedriveAllowPolicy)
213 .map(|policy| {
214 serde_json::from_str::<serde_json::Value>(policy)
216 .ok()
217 .and_then(|v| v.get("redrivePermission").and_then(|p| p.as_str()).map(String::from))
218 .unwrap_or_else(|| policy.to_string())
219 })
220 .unwrap_or_else(|| "-".to_string()),
221 redrive_policy: attrs
222 .get(&aws_sdk_sqs::types::QueueAttributeName::RedrivePolicy)
223 .map(|v| v.to_string())
224 .unwrap_or_else(|| "".to_string()),
225 redrive_task_id: "-".to_string(),
227 redrive_task_start_time: "-".to_string(),
228 redrive_task_status: "-".to_string(),
229 redrive_task_percent: "-".to_string(),
230 redrive_task_destination: "-".to_string(),
231 });
232 }
233 }
234
235 queues.sort_by(|a, b| a.name.cmp(&b.name));
236 Ok(queues)
237 }
238
239 pub async fn list_message_move_tasks(
240 &self,
241 queue_url: &str,
242 ) -> Result<(String, String, String, String, String)> {
243 let client = self.config.sqs_client();
244
245 match client
246 .list_message_move_tasks()
247 .source_arn(queue_url)
248 .send()
249 .await
250 {
251 Ok(response) => {
252 let results = response.results();
253 if let Some(task) = results.first() {
254 let task_id = task.task_handle().unwrap_or("-").to_string();
255
256 let start_time = chrono::DateTime::from_timestamp(task.started_timestamp(), 0)
257 .map(|dt| format!("{} (UTC)", dt.format("%Y-%m-%d %H:%M:%S")))
258 .unwrap_or_else(|| "-".to_string());
259
260 let status = format!("{:?}", task.status());
261
262 let moved = task.approximate_number_of_messages_moved();
263 let total = task.approximate_number_of_messages_to_move().unwrap_or(0);
264 let percent = if total > 0 {
265 format!("{}%", (moved * 100) / total)
266 } else {
267 "-".to_string()
268 };
269
270 let destination = task.destination_arn().unwrap_or("-").to_string();
271
272 return Ok((task_id, start_time, status, percent, destination));
273 }
274 }
275 Err(_) => {
276 }
278 }
279
280 Ok((
281 "-".to_string(),
282 "-".to_string(),
283 "-".to_string(),
284 "-".to_string(),
285 "-".to_string(),
286 ))
287 }
288
289 pub async fn get_queue_arn(&self, queue_url: &str) -> Result<String> {
290 let client = self.config.sqs_client();
291
292 let response = client
293 .get_queue_attributes()
294 .queue_url(queue_url)
295 .attribute_names(aws_sdk_sqs::types::QueueAttributeName::QueueArn)
296 .send()
297 .await?;
298
299 let arn = response
300 .attributes()
301 .and_then(|attrs| attrs.get(&aws_sdk_sqs::types::QueueAttributeName::QueueArn))
302 .map(|v| v.to_string())
303 .unwrap_or_default();
304
305 Ok(arn)
306 }
307
308 pub async fn list_pipes(&self, queue_arn: &str) -> Result<Vec<EventBridgePipe>> {
309 let pipes_client = self.config.pipes_client();
310
311 let response = pipes_client
312 .list_pipes()
313 .source_prefix(queue_arn)
314 .send()
315 .await?;
316
317 let mut pipes = Vec::new();
318 if let Some(pipe_list) = response.pipes {
319 for pipe in pipe_list {
320 pipes.push(EventBridgePipe {
321 name: pipe.name.unwrap_or_default(),
322 status: pipe
323 .current_state
324 .map(|s| s.as_str().to_string())
325 .unwrap_or_default(),
326 target: pipe.target.unwrap_or_default(),
327 last_modified: pipe
328 .last_modified_time
329 .map(|dt| dt.secs().to_string())
330 .unwrap_or_default(),
331 });
332 }
333 }
334
335 Ok(pipes)
336 }
337
338 pub async fn list_tags(&self, queue_arn: &str) -> Result<Vec<QueueTag>> {
339 let client = self.config.sqs_client();
340
341 let response = client.list_queue_tags().queue_url(queue_arn).send().await?;
342
343 let mut tags = Vec::new();
344 if let Some(tag_map) = response.tags {
345 for (key, value) in tag_map {
346 tags.push(QueueTag { key, value });
347 }
348 }
349
350 Ok(tags)
351 }
352
353 pub async fn get_queue_metrics(&self, queue_name: &str) -> Result<Vec<(i64, f64)>> {
354 let query = MetricQuery::new(
355 "AWS/SQS",
356 "ApproximateAgeOfOldestMessage",
357 Statistic::Maximum,
358 )
359 .dimension("QueueName", queue_name);
360 MetricReader::new(self.config.cloudwatch_client())
361 .recent(query, 60)
362 .await
363 }
364
365 pub async fn get_queue_delayed_metrics(&self, queue_name: &str) -> Result<Vec<(i64, f64)>> {
366 let query = MetricQuery::new(
367 "AWS/SQS",
368 "ApproximateNumberOfMessagesDelayed",
369 Statistic::Average,
370 )
371 .dimension("QueueName", queue_name);
372 MetricReader::new(self.config.cloudwatch_client())
373 .recent(query, 60)
374 .await
375 }
376
377 pub async fn get_queue_not_visible_metrics(&self, queue_name: &str) -> Result<Vec<(i64, f64)>> {
378 let query = MetricQuery::new(
379 "AWS/SQS",
380 "ApproximateNumberOfMessagesNotVisible",
381 Statistic::Average,
382 )
383 .dimension("QueueName", queue_name);
384 MetricReader::new(self.config.cloudwatch_client())
385 .recent(query, 60)
386 .await
387 }
388
389 pub async fn get_queue_visible_metrics(&self, queue_name: &str) -> Result<Vec<(i64, f64)>> {
390 let query = MetricQuery::new(
391 "AWS/SQS",
392 "ApproximateNumberOfMessagesVisible",
393 Statistic::Average,
394 )
395 .dimension("QueueName", queue_name);
396 MetricReader::new(self.config.cloudwatch_client())
397 .recent(query, 60)
398 .await
399 }
400
401 pub async fn get_queue_empty_receives_metrics(
402 &self,
403 queue_name: &str,
404 ) -> Result<Vec<(i64, f64)>> {
405 let query = MetricQuery::new("AWS/SQS", "NumberOfEmptyReceives", Statistic::Sum)
406 .dimension("QueueName", queue_name);
407 MetricReader::new(self.config.cloudwatch_client())
408 .recent(query, 60)
409 .await
410 }
411
412 pub async fn get_queue_messages_deleted_metrics(
413 &self,
414 queue_name: &str,
415 ) -> Result<Vec<(i64, f64)>> {
416 let query = MetricQuery::new("AWS/SQS", "NumberOfMessagesDeleted", Statistic::Sum)
417 .dimension("QueueName", queue_name);
418 MetricReader::new(self.config.cloudwatch_client())
419 .recent(query, 60)
420 .await
421 }
422
423 pub async fn get_queue_messages_received_metrics(
424 &self,
425 queue_name: &str,
426 ) -> Result<Vec<(i64, f64)>> {
427 let query = MetricQuery::new("AWS/SQS", "NumberOfMessagesReceived", Statistic::Sum)
428 .dimension("QueueName", queue_name);
429 MetricReader::new(self.config.cloudwatch_client())
430 .recent(query, 60)
431 .await
432 }
433
434 pub async fn get_queue_messages_sent_metrics(
435 &self,
436 queue_name: &str,
437 ) -> Result<Vec<(i64, f64)>> {
438 let query = MetricQuery::new("AWS/SQS", "NumberOfMessagesSent", Statistic::Sum)
439 .dimension("QueueName", queue_name);
440 MetricReader::new(self.config.cloudwatch_client())
441 .recent(query, 60)
442 .await
443 }
444
445 pub async fn get_queue_sent_message_size_metrics(
446 &self,
447 queue_name: &str,
448 ) -> Result<Vec<(i64, f64)>> {
449 let query = MetricQuery::new("AWS/SQS", "SentMessageSize", Statistic::Average)
450 .dimension("QueueName", queue_name);
451 MetricReader::new(self.config.cloudwatch_client())
452 .recent(query, 60)
453 .await
454 }
455}