1use std::{
2 collections::HashMap,
3 future::ready,
4 num::NonZeroUsize,
5 panic,
6 sync::{Arc, LazyLock},
7 time::{Duration, Instant},
8};
9
10use aws_sdk_s3::{Client as S3Client, operation::get_object::GetObjectError};
11use aws_sdk_sqs::{
12 Client as SqsClient,
13 operation::{
14 delete_message_batch::{DeleteMessageBatchError, DeleteMessageBatchOutput},
15 receive_message::ReceiveMessageError,
16 send_message_batch::{SendMessageBatchError, SendMessageBatchOutput},
17 },
18 types::{DeleteMessageBatchRequestEntry, Message, SendMessageBatchRequestEntry},
19};
20use aws_smithy_runtime_api::client::{orchestrator::HttpResponse, result::SdkError};
21use aws_types::region::Region;
22use bytes::Bytes;
23use chrono::{DateTime, TimeZone, Utc};
24use futures::{FutureExt, Stream, StreamExt, TryFutureExt};
25use serde::{Deserialize, Deserializer, Serialize, Serializer};
26use serde_with::serde_as;
27use smallvec::SmallVec;
28use snafu::{ResultExt, Snafu};
29use tokio::{pin, select};
30use tokio_util::codec::FramedRead;
31use vector_lib::{
32 codecs::decoding::FramingError,
33 config::{LegacyKey, LogNamespace, log_schema},
34 configurable::configurable_component,
35 event::MaybeAsLogMut,
36 internal_event::{
37 ByteSize, BytesReceived, CountByteSize, InternalEventHandle as _, Protocol, Registered,
38 },
39 lookup::{PathPrefix, metadata_path, path},
40 source_sender::SendError,
41};
42
43use crate::{
44 SourceSender,
45 aws::AwsTimeout,
46 codecs::Decoder,
47 common::backoff::ExponentialBackoff,
48 config::{SourceAcknowledgementsConfig, SourceContext},
49 event::{BatchNotifier, BatchStatus, EstimatedJsonEncodedSizeOf, Event, LogEvent},
50 internal_events::{
51 EventsReceived, S3ObjectProcessingFailed, S3ObjectProcessingSucceeded,
52 SqsMessageDeleteBatchError, SqsMessageDeletePartialError, SqsMessageDeleteSucceeded,
53 SqsMessageProcessingError, SqsMessageProcessingSucceeded, SqsMessageReceiveError,
54 SqsMessageReceiveSucceeded, SqsMessageSendBatchError, SqsMessageSentPartialError,
55 SqsMessageSentSucceeded, SqsS3EventRecordInvalidEventIgnored, StreamClosedError,
56 },
57 line_agg::{self, LineAgg},
58 shutdown::ShutdownSignal,
59 sources::aws_s3::AwsS3Config,
60 tls::TlsConfig,
61};
62
63static SUPPORTED_S3_EVENT_VERSION: LazyLock<semver::VersionReq> =
64 LazyLock::new(|| semver::VersionReq::parse("~2").unwrap());
65
66#[serde_as]
68#[configurable_component]
69#[derive(Clone, Debug, Default)]
70#[serde(deny_unknown_fields)]
71pub(super) struct DeferredConfig {
72 #[configurable(metadata(
74 docs::examples = "https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue"
75 ))]
76 #[configurable(validation(format = "uri"))]
77 pub(super) queue_url: String,
78
79 #[configurable(metadata(docs::type_unit = "seconds"))]
83 #[configurable(metadata(docs::examples = 3600))]
84 pub(super) max_age_secs: u64,
85}
86
87#[serde_as]
92#[configurable_component]
93#[derive(Clone, Debug, Derivative)]
94#[derivative(Default)]
95#[serde(deny_unknown_fields)]
96pub(super) struct Config {
97 #[configurable(metadata(
99 docs::examples = "https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue"
100 ))]
101 #[configurable(validation(format = "uri"))]
102 pub(super) queue_url: String,
103
104 #[serde(default = "default_poll_secs")]
111 #[derivative(Default(value = "default_poll_secs()"))]
112 #[configurable(metadata(docs::type_unit = "seconds"))]
113 pub(super) poll_secs: u32,
114
115 #[serde(default = "default_visibility_timeout_secs")]
124 #[derivative(Default(value = "default_visibility_timeout_secs()"))]
125 #[configurable(metadata(docs::type_unit = "seconds"))]
126 #[configurable(metadata(docs::human_name = "Visibility Timeout"))]
127 pub(super) visibility_timeout_secs: u32,
128
129 #[serde(default = "default_true")]
133 #[derivative(Default(value = "default_true()"))]
134 pub(super) delete_message: bool,
135
136 #[serde(default = "default_true")]
140 #[derivative(Default(value = "default_true()"))]
141 pub(super) delete_failed_message: bool,
142
143 #[configurable(metadata(docs::type_unit = "tasks"))]
153 #[configurable(metadata(docs::examples = 5))]
154 pub(super) client_concurrency: Option<NonZeroUsize>,
155
156 #[serde(default = "default_max_number_of_messages")]
164 #[derivative(Default(value = "default_max_number_of_messages()"))]
165 #[configurable(metadata(docs::human_name = "Max Messages"))]
166 #[configurable(metadata(docs::examples = 1))]
167 pub(super) max_number_of_messages: u32,
168
169 #[configurable(derived)]
170 #[serde(default)]
171 #[derivative(Default)]
172 pub(super) tls_options: Option<TlsConfig>,
173
174 #[configurable(derived)]
177 #[derivative(Default)]
178 #[serde(default)]
179 #[serde(flatten)]
180 pub(super) timeout: Option<AwsTimeout>,
181
182 #[configurable(derived)]
184 pub(super) deferred: Option<DeferredConfig>,
185}
186
187pub(super) struct S3Options {
188 pub(super) compression: super::Compression,
189 pub(super) request_payer: Option<super::S3RequestPayer>,
190}
191
192const fn default_poll_secs() -> u32 {
193 15
194}
195
196const fn default_visibility_timeout_secs() -> u32 {
197 300
198}
199
200const fn default_max_number_of_messages() -> u32 {
201 10
202}
203
204const fn default_true() -> bool {
205 true
206}
207
208#[derive(Debug, Snafu)]
209pub(super) enum IngestorNewError {
210 #[snafu(display("Invalid value for max_number_of_messages {}", messages))]
211 InvalidNumberOfMessages { messages: u32 },
212}
213
214#[allow(clippy::large_enum_variant)]
215#[derive(Debug, Snafu)]
216pub enum ProcessingError {
217 #[snafu(display(
218 "Could not parse SQS message with id {} as S3 notification: {}",
219 message_id,
220 source
221 ))]
222 InvalidSqsMessage {
223 source: serde_json::Error,
224 message_id: String,
225 },
226 #[snafu(display("Failed to fetch s3://{}/{}: {}", bucket, key, source))]
227 GetObject {
228 source: SdkError<GetObjectError, HttpResponse>,
229 bucket: String,
230 key: String,
231 },
232 #[snafu(display("Failed to read all of s3://{}/{}: {}", bucket, key, source))]
233 ReadObject {
234 source: Box<dyn FramingError>,
235 bucket: String,
236 key: String,
237 },
238 #[snafu(display("Failed to flush all of s3://{}/{}: {}", bucket, key, source))]
239 PipelineSend {
240 source: vector_lib::source_sender::SendError,
241 bucket: String,
242 key: String,
243 },
244 #[snafu(display(
245 "Object notification for s3://{}/{} is a bucket in another region: {}",
246 bucket,
247 key,
248 region
249 ))]
250 WrongRegion {
251 region: String,
252 bucket: String,
253 key: String,
254 },
255 #[snafu(display("Unsupported S3 event version: {}.", version,))]
256 UnsupportedS3EventVersion { version: semver::Version },
257 #[snafu(display(
258 "Sink reported an error sending events for an s3 object in region {}: s3://{}/{}",
259 region,
260 bucket,
261 key
262 ))]
263 ErrorAcknowledgement {
264 region: String,
265 bucket: String,
266 key: String,
267 },
268 #[snafu(display(
269 "File s3://{}/{} too old. Forwarded to deferred queue {}",
270 bucket,
271 key,
272 deferred_queue
273 ))]
274 FileTooOld {
275 bucket: String,
276 key: String,
277 deferred_queue: String,
278 },
279}
280
281pub struct State {
282 region: Region,
283
284 s3_client: S3Client,
285 s3_options: S3Options,
286 sqs_client: SqsClient,
287
288 multiline: Option<line_agg::Config>,
289
290 queue_url: String,
291 poll_secs: i32,
292 max_number_of_messages: i32,
293 client_concurrency: usize,
294 visibility_timeout_secs: i32,
295 delete_message: bool,
296 delete_failed_message: bool,
297 decoder: Decoder,
298
299 deferred: Option<DeferredConfig>,
300}
301
302pub(super) struct Ingestor {
303 state: Arc<State>,
304}
305
306impl Ingestor {
307 pub(super) async fn new(
308 region: Region,
309 sqs_client: SqsClient,
310 s3_client: S3Client,
311 config: Config,
312 s3_options: S3Options,
313 multiline: Option<line_agg::Config>,
314 decoder: Decoder,
315 ) -> Result<Ingestor, IngestorNewError> {
316 if config.max_number_of_messages < 1 || config.max_number_of_messages > 10 {
317 return Err(IngestorNewError::InvalidNumberOfMessages {
318 messages: config.max_number_of_messages,
319 });
320 }
321 let state = Arc::new(State {
322 region,
323
324 s3_client,
325 s3_options,
326 sqs_client,
327
328 multiline,
329
330 queue_url: config.queue_url,
331 poll_secs: config.poll_secs as i32,
332 max_number_of_messages: config.max_number_of_messages as i32,
333 client_concurrency: config
334 .client_concurrency
335 .map(|n| n.get())
336 .unwrap_or_else(crate::num_threads),
337 visibility_timeout_secs: config.visibility_timeout_secs as i32,
338 delete_message: config.delete_message,
339 delete_failed_message: config.delete_failed_message,
340 decoder,
341
342 deferred: config.deferred,
343 });
344
345 Ok(Ingestor { state })
346 }
347
348 pub(super) async fn run(
349 self,
350 cx: SourceContext,
351 acknowledgements: SourceAcknowledgementsConfig,
352 log_namespace: LogNamespace,
353 ) -> Result<(), ()> {
354 let acknowledgements = cx.do_acknowledgements(acknowledgements);
355 let mut handles = Vec::new();
356 for _ in 0..self.state.client_concurrency {
357 let process = IngestorProcess::new(
358 Arc::clone(&self.state),
359 cx.out.clone(),
360 cx.shutdown.clone(),
361 log_namespace,
362 acknowledgements,
363 );
364 let fut = process.run();
365 let handle = crate::spawn_in_current_span(fut);
366 handles.push(handle);
367 }
368
369 for handle in handles.drain(..) {
372 if let Err(e) = handle.await
373 && e.is_panic()
374 {
375 panic::resume_unwind(e.into_panic());
376 }
377 }
378
379 Ok(())
380 }
381}
382
383pub struct IngestorProcess {
384 state: Arc<State>,
385 out: SourceSender,
386 shutdown: ShutdownSignal,
387 acknowledgements: bool,
388 log_namespace: LogNamespace,
389 bytes_received: Registered<BytesReceived>,
390 events_received: Registered<EventsReceived>,
391 backoff: ExponentialBackoff,
392}
393
394impl IngestorProcess {
395 pub fn new(
396 state: Arc<State>,
397 out: SourceSender,
398 shutdown: ShutdownSignal,
399 log_namespace: LogNamespace,
400 acknowledgements: bool,
401 ) -> Self {
402 Self {
403 state,
404 out,
405 shutdown,
406 acknowledgements,
407 log_namespace,
408 bytes_received: register!(BytesReceived::from(Protocol::HTTP)),
409 events_received: register!(EventsReceived),
410 backoff: ExponentialBackoff::default().max_delay(Duration::from_secs(30)),
411 }
412 }
413
414 async fn run(mut self) {
415 let shutdown = self.shutdown.clone().fuse();
416 pin!(shutdown);
417
418 loop {
419 select! {
420 _ = &mut shutdown => break,
421 result = self.run_once() => {
422 match result {
423 Ok(()) => {
424 self.backoff.reset();
426 }
427 Err(_) => {
428 let delay = self.backoff.next().expect("backoff never ends");
429 trace!(
430 delay_ms = delay.as_millis(),
431 "`run_once` failed, will retry after delay.",
432 );
433 tokio::time::sleep(delay).await;
434 }
435 }
436 },
437 }
438 }
439 }
440
441 async fn run_once(&mut self) -> Result<(), ()> {
442 let messages = match self.receive_messages().await {
443 Ok(messages) => {
444 emit!(SqsMessageReceiveSucceeded {
445 count: messages.len(),
446 });
447 messages
448 }
449 Err(err) => {
450 emit!(SqsMessageReceiveError { error: &err });
451 return Err(());
452 }
453 };
454
455 let mut delete_entries = Vec::new();
456 let mut deferred_entries = Vec::new();
457 for message in messages {
458 let receipt_handle = match message.receipt_handle {
459 None => {
460 warn!(message = "Refusing to process message with no receipt_handle.", ?message.message_id);
464 continue;
465 }
466 Some(ref handle) => handle.to_owned(),
467 };
468
469 let message_id = message
470 .message_id
471 .clone()
472 .unwrap_or_else(|| "<unknown>".to_owned());
473 match self.handle_sqs_message(message.clone()).await {
474 Ok(()) => {
475 emit!(SqsMessageProcessingSucceeded {
476 message_id: &message_id
477 });
478 if self.state.delete_message {
479 trace!(
480 message = "Queued SQS message for deletion.",
481 id = message_id,
482 receipt_handle = receipt_handle,
483 );
484 delete_entries.push(
485 DeleteMessageBatchRequestEntry::builder()
486 .id(message_id.clone())
487 .receipt_handle(receipt_handle)
488 .build()
489 .expect("all required builder params specified"),
490 );
491 }
492 }
493 Err(err) => {
494 match err {
495 ProcessingError::FileTooOld { .. } => {
496 emit!(SqsMessageProcessingSucceeded {
497 message_id: &message_id
498 });
499 if let Some(deferred) = &self.state.deferred {
500 trace!(
501 message = "Forwarding message to deferred queue.",
502 id = message_id,
503 receipt_handle = receipt_handle,
504 deferred_queue = deferred.queue_url,
505 );
506
507 deferred_entries.push(
508 SendMessageBatchRequestEntry::builder()
509 .id(message_id.clone())
510 .message_body(message.body.unwrap_or_default())
511 .build()
512 .expect("all required builder params specified"),
513 );
514 }
515 if self.state.delete_message {
517 trace!(
518 message = "Queued SQS message for deletion.",
519 id = message_id,
520 receipt_handle = receipt_handle,
521 );
522 delete_entries.push(
523 DeleteMessageBatchRequestEntry::builder()
524 .id(message_id)
525 .receipt_handle(receipt_handle)
526 .build()
527 .expect("all required builder params specified"),
528 );
529 }
530 }
531 _ => {
532 emit!(SqsMessageProcessingError {
533 message_id: &message_id,
534 error: &err,
535 });
536 }
537 }
538 }
539 }
540 }
541
542 if !deferred_entries.is_empty() {
544 let Some(deferred) = &self.state.deferred else {
545 warn!("Deferred queue not configured, but received deferred entries.");
546 return Ok(());
547 };
548 let cloned_entries = deferred_entries.clone();
549 match self
550 .send_messages(deferred_entries, deferred.queue_url.clone())
551 .await
552 {
553 Ok(result) => {
554 if !result.successful.is_empty() {
555 emit!(SqsMessageSentSucceeded {
556 message_ids: result.successful,
557 })
558 }
559
560 if !result.failed.is_empty() {
561 emit!(SqsMessageSentPartialError {
562 entries: result.failed
563 })
564 }
565 }
566 Err(err) => {
567 emit!(SqsMessageSendBatchError {
568 entries: cloned_entries,
569 error: err,
570 });
571 }
572 }
573 }
574 if !delete_entries.is_empty() {
575 let cloned_entries = delete_entries.clone();
577 match self.delete_messages(delete_entries).await {
578 Ok(result) => {
579 if !result.successful.is_empty() {
582 emit!(SqsMessageDeleteSucceeded {
583 message_ids: result.successful,
584 });
585 }
586
587 if !result.failed.is_empty() {
588 emit!(SqsMessageDeletePartialError {
589 entries: result.failed
590 });
591 }
592 }
593 Err(err) => {
594 emit!(SqsMessageDeleteBatchError {
595 entries: cloned_entries,
596 error: err,
597 });
598 }
599 }
600 }
601 Ok(())
602 }
603
604 async fn handle_sqs_message(&mut self, message: Message) -> Result<(), ProcessingError> {
605 let sqs_body = message.body.unwrap_or_default();
606 let sqs_body = serde_json::from_str::<SnsNotification>(sqs_body.as_ref())
607 .map(|notification| notification.message)
608 .unwrap_or(sqs_body);
609 let s3_event: SqsEvent =
610 serde_json::from_str(sqs_body.as_ref()).context(InvalidSqsMessageSnafu {
611 message_id: message
612 .message_id
613 .clone()
614 .unwrap_or_else(|| "<empty>".to_owned()),
615 })?;
616
617 match s3_event {
618 SqsEvent::TestEvent(_s3_test_event) => {
619 debug!(?message.message_id, message = "Found S3 Test Event.");
620 Ok(())
621 }
622 SqsEvent::Event(s3_event) => self.handle_s3_event(s3_event).await,
623 }
624 }
625
626 async fn handle_s3_event(&mut self, s3_event: S3Event) -> Result<(), ProcessingError> {
627 for record in s3_event.records {
628 self.handle_s3_event_record(record, self.log_namespace)
629 .await?
630 }
631 Ok(())
632 }
633
634 async fn handle_s3_event_record(
635 &mut self,
636 s3_event: S3EventRecord,
637 log_namespace: LogNamespace,
638 ) -> Result<(), ProcessingError> {
639 let event_version: semver::Version = s3_event.event_version.clone().into();
640 if !SUPPORTED_S3_EVENT_VERSION.matches(&event_version) {
641 return Err(ProcessingError::UnsupportedS3EventVersion {
642 version: event_version.clone(),
643 });
644 }
645
646 if s3_event.event_name.kind != "ObjectCreated" {
647 emit!(SqsS3EventRecordInvalidEventIgnored {
648 bucket: &s3_event.s3.bucket.name,
649 key: &s3_event.s3.object.key,
650 kind: &s3_event.event_name.kind,
651 name: &s3_event.event_name.name,
652 });
653 return Ok(());
654 }
655
656 if self.state.region.as_ref() != s3_event.aws_region.as_str() {
659 return Err(ProcessingError::WrongRegion {
660 bucket: s3_event.s3.bucket.name.clone(),
661 key: s3_event.s3.object.key.clone(),
662 region: s3_event.aws_region,
663 });
664 }
665
666 if let Some(deferred) = &self.state.deferred {
667 let delta = Utc::now() - s3_event.event_time;
668 if delta.num_seconds() > deferred.max_age_secs as i64 {
669 return Err(ProcessingError::FileTooOld {
670 bucket: s3_event.s3.bucket.name.clone(),
671 key: s3_event.s3.object.key.clone(),
672 deferred_queue: deferred.queue_url.clone(),
673 });
674 }
675 }
676
677 let download_start = Instant::now();
678
679 let object_result = self
680 .state
681 .s3_client
682 .get_object()
683 .bucket(s3_event.s3.bucket.name.clone())
684 .key(s3_event.s3.object.key.clone())
685 .set_request_payer(self.state.s3_options.request_payer.map(Into::into))
686 .send()
687 .await
688 .context(GetObjectSnafu {
689 bucket: s3_event.s3.bucket.name.clone(),
690 key: s3_event.s3.object.key.clone(),
691 });
692
693 let object = object_result?;
694
695 debug!(
696 message = "Got S3 object from SQS notification.",
697 bucket = s3_event.s3.bucket.name,
698 key = s3_event.s3.object.key,
699 );
700
701 let metadata = object.metadata;
702
703 let timestamp = object.last_modified.map(|ts| {
704 Utc.timestamp_opt(ts.secs(), ts.subsec_nanos())
705 .single()
706 .expect("invalid timestamp")
707 });
708
709 let (batch, receiver) = BatchNotifier::maybe_new_with_receiver(self.acknowledgements);
710 let object_reader = super::s3_object_decoder(
711 self.state.s3_options.compression,
712 &s3_event.s3.object.key,
713 object.content_encoding.as_deref(),
714 object.content_type.as_deref(),
715 object.body,
716 )
717 .await;
718
719 let mut read_error = None;
732 let bytes_received = self.bytes_received.clone();
733 let events_received = self.events_received.clone();
734 let lines: Box<dyn Stream<Item = Bytes> + Send + Unpin> = Box::new(
735 FramedRead::new(object_reader, self.state.decoder.framer.clone())
736 .map(|res| {
737 res.inspect(|bytes| {
738 bytes_received.emit(ByteSize(bytes.len()));
739 })
740 .map_err(|err| {
741 read_error = Some(err);
742 })
743 .ok()
744 })
745 .take_while(|res| ready(res.is_some()))
746 .map(|r| r.expect("validated by take_while")),
747 );
748
749 let lines: Box<dyn Stream<Item = Bytes> + Send + Unpin> = match &self.state.multiline {
750 Some(config) => Box::new(
751 LineAgg::new(
752 lines.map(|line| ((), line, ())),
753 line_agg::Logic::new(config.clone()),
754 )
755 .map(|(_src, line, _context, _lastline_context)| line),
756 ),
757 None => lines,
758 };
759
760 let mut stream = lines.flat_map(|line| {
761 let events = match self.state.decoder.deserializer_parse(line) {
762 Ok((events, _events_size)) => events,
763 Err(_error) => {
764 SmallVec::new()
767 }
768 };
769
770 let events = events
771 .into_iter()
772 .map(|mut event: Event| {
773 event = event.with_batch_notifier_option(&batch);
774 if let Some(log_event) = event.maybe_as_log_mut() {
775 handle_single_log(
776 log_event,
777 log_namespace,
778 &s3_event,
779 &metadata,
780 timestamp,
781 );
782 }
783 events_received.emit(CountByteSize(1, event.estimated_json_encoded_size_of()));
784 event
785 })
786 .collect::<Vec<Event>>();
787 futures::stream::iter(events)
788 });
789
790 let send_error = match self.out.send_event_stream(&mut stream).await {
791 Ok(_) => None,
792 Err(SendError::Closed) => {
793 let (count, _) = stream.size_hint();
794 emit!(StreamClosedError { count });
795 Some(SendError::Closed)
796 }
797 Err(SendError::Timeout) => unreachable!("No timeout is configured here"),
798 };
799
800 drop(stream);
803
804 let bucket = &s3_event.s3.bucket.name;
805 let duration = download_start.elapsed();
806
807 if read_error.is_some() {
808 emit!(S3ObjectProcessingFailed { bucket, duration });
809 } else {
810 emit!(S3ObjectProcessingSucceeded { bucket, duration });
811 }
812
813 drop(batch);
816
817 if let Some(error) = read_error {
818 Err(ProcessingError::ReadObject {
819 source: error,
820 bucket: s3_event.s3.bucket.name.clone(),
821 key: s3_event.s3.object.key.clone(),
822 })
823 } else if let Some(error) = send_error {
824 Err(ProcessingError::PipelineSend {
825 source: error,
826 bucket: s3_event.s3.bucket.name.clone(),
827 key: s3_event.s3.object.key.clone(),
828 })
829 } else {
830 match receiver {
831 None => Ok(()),
832 Some(receiver) => {
833 let result = receiver.await;
834 match result {
835 BatchStatus::Delivered => {
836 debug!(
837 message = "S3 object from SQS delivered.",
838 bucket = s3_event.s3.bucket.name,
839 key = s3_event.s3.object.key,
840 );
841 Ok(())
842 }
843 BatchStatus::Errored => Err(ProcessingError::ErrorAcknowledgement {
844 bucket: s3_event.s3.bucket.name,
845 key: s3_event.s3.object.key,
846 region: s3_event.aws_region,
847 }),
848 BatchStatus::Rejected => {
849 if self.state.delete_failed_message {
850 warn!(
851 message =
852 "S3 object from SQS was rejected. Deleting failed message.",
853 bucket = s3_event.s3.bucket.name,
854 key = s3_event.s3.object.key,
855 );
856 Ok(())
857 } else {
858 Err(ProcessingError::ErrorAcknowledgement {
859 bucket: s3_event.s3.bucket.name,
860 key: s3_event.s3.object.key,
861 region: s3_event.aws_region,
862 })
863 }
864 }
865 }
866 }
867 }
868 }
869 }
870
871 async fn receive_messages(
872 &mut self,
873 ) -> Result<Vec<Message>, SdkError<ReceiveMessageError, HttpResponse>> {
874 self.state
875 .sqs_client
876 .receive_message()
877 .queue_url(self.state.queue_url.clone())
878 .max_number_of_messages(self.state.max_number_of_messages)
879 .visibility_timeout(self.state.visibility_timeout_secs)
880 .wait_time_seconds(self.state.poll_secs)
881 .send()
882 .map_ok(|res| res.messages.unwrap_or_default())
883 .await
884 }
885
886 async fn delete_messages(
887 &mut self,
888 entries: Vec<DeleteMessageBatchRequestEntry>,
889 ) -> Result<DeleteMessageBatchOutput, SdkError<DeleteMessageBatchError, HttpResponse>> {
890 self.state
891 .sqs_client
892 .delete_message_batch()
893 .queue_url(self.state.queue_url.clone())
894 .set_entries(Some(entries))
895 .send()
896 .await
897 }
898
899 async fn send_messages(
900 &mut self,
901 entries: Vec<SendMessageBatchRequestEntry>,
902 queue_url: String,
903 ) -> Result<SendMessageBatchOutput, SdkError<SendMessageBatchError, HttpResponse>> {
904 self.state
905 .sqs_client
906 .send_message_batch()
907 .queue_url(queue_url.clone())
908 .set_entries(Some(entries))
909 .send()
910 .await
911 }
912}
913
914fn handle_single_log(
915 log: &mut LogEvent,
916 log_namespace: LogNamespace,
917 s3_event: &S3EventRecord,
918 metadata: &Option<HashMap<String, String>>,
919 timestamp: Option<DateTime<Utc>>,
920) {
921 log_namespace.insert_source_metadata(
922 AwsS3Config::NAME,
923 log,
924 Some(LegacyKey::Overwrite(path!("bucket"))),
925 path!("bucket"),
926 Bytes::from(s3_event.s3.bucket.name.as_bytes().to_vec()),
927 );
928
929 log_namespace.insert_source_metadata(
930 AwsS3Config::NAME,
931 log,
932 Some(LegacyKey::Overwrite(path!("object"))),
933 path!("object"),
934 Bytes::from(s3_event.s3.object.key.as_bytes().to_vec()),
935 );
936 log_namespace.insert_source_metadata(
937 AwsS3Config::NAME,
938 log,
939 Some(LegacyKey::Overwrite(path!("region"))),
940 path!("region"),
941 Bytes::from(s3_event.aws_region.as_bytes().to_vec()),
942 );
943
944 if let Some(metadata) = metadata {
945 for (key, value) in metadata {
946 log_namespace.insert_source_metadata(
947 AwsS3Config::NAME,
948 log,
949 Some(LegacyKey::Overwrite(path!(key))),
950 path!("metadata", key.as_str()),
951 value.clone(),
952 );
953 }
954 }
955
956 log_namespace.insert_vector_metadata(
957 log,
958 log_schema().source_type_key(),
959 path!("source_type"),
960 Bytes::from_static(AwsS3Config::NAME.as_bytes()),
961 );
962
963 match log_namespace {
967 LogNamespace::Vector => {
968 if let Some(timestamp) = timestamp {
969 log.insert(metadata_path!(AwsS3Config::NAME, "timestamp"), timestamp);
970 }
971
972 log.insert(metadata_path!("vector", "ingest_timestamp"), Utc::now());
973 }
974 LogNamespace::Legacy => {
975 if let Some(timestamp_key) = log_schema().timestamp_key() {
976 log.try_insert(
977 (PathPrefix::Event, timestamp_key),
978 timestamp.unwrap_or_else(Utc::now),
979 );
980 }
981 }
982 };
983}
984
985#[derive(Clone, Debug, Deserialize)]
987#[serde(rename_all = "PascalCase")]
988pub struct SnsNotification {
989 pub message: String,
990 pub timestamp: DateTime<Utc>,
991}
992
993#[derive(Clone, Debug, Deserialize)]
995#[serde(untagged)]
996enum SqsEvent {
997 Event(S3Event),
998 TestEvent(S3TestEvent),
999}
1000
1001#[derive(Clone, Debug, Deserialize)]
1002#[serde(rename_all = "PascalCase")]
1003pub struct S3TestEvent {
1004 pub service: String,
1005 pub event: S3EventName,
1006 pub bucket: String,
1007}
1008
1009#[derive(Clone, Debug, Deserialize, Serialize)]
1011#[serde(rename_all = "PascalCase")]
1012pub struct S3Event {
1013 pub records: Vec<S3EventRecord>,
1014}
1015
1016#[derive(Clone, Debug, Deserialize, Serialize)]
1017#[serde(rename_all = "camelCase")]
1018pub struct S3EventRecord {
1019 pub event_version: S3EventVersion,
1020 pub event_source: String,
1021 pub aws_region: String,
1022 pub event_name: S3EventName,
1023 pub event_time: DateTime<Utc>,
1024
1025 pub s3: S3Message,
1026}
1027
1028#[derive(Clone, Debug)]
1029pub struct S3EventVersion {
1030 pub major: u64,
1031 pub minor: u64,
1032}
1033
1034impl From<S3EventVersion> for semver::Version {
1035 fn from(v: S3EventVersion) -> semver::Version {
1036 semver::Version::new(v.major, v.minor, 0)
1037 }
1038}
1039
1040impl<'de> Deserialize<'de> for S3EventVersion {
1043 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1044 where
1045 D: Deserializer<'de>,
1046 {
1047 use serde::de::Error;
1048
1049 let s = String::deserialize(deserializer)?;
1050
1051 let mut parts = s.splitn(2, '.');
1052
1053 let major = parts
1054 .next()
1055 .ok_or_else(|| D::Error::custom("Missing major version number"))?
1056 .parse::<u64>()
1057 .map_err(D::Error::custom)?;
1058
1059 let minor = parts
1060 .next()
1061 .ok_or_else(|| D::Error::custom("Missing minor version number"))?
1062 .parse::<u64>()
1063 .map_err(D::Error::custom)?;
1064
1065 Ok(S3EventVersion { major, minor })
1066 }
1067}
1068
1069impl Serialize for S3EventVersion {
1070 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1071 where
1072 S: Serializer,
1073 {
1074 serializer.serialize_str(&format!("{}.{}", self.major, self.minor))
1075 }
1076}
1077
1078#[derive(Clone, Debug)]
1079pub struct S3EventName {
1080 pub kind: String,
1081 pub name: String,
1082}
1083
1084impl<'de> Deserialize<'de> for S3EventName {
1089 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1090 where
1091 D: Deserializer<'de>,
1092 {
1093 use serde::de::Error;
1094
1095 let s = String::deserialize(deserializer)?;
1096
1097 let mut parts = s.splitn(2, ':');
1098
1099 let kind = parts
1100 .next()
1101 .ok_or_else(|| D::Error::custom("Missing event kind"))?
1102 .parse::<String>()
1103 .map_err(D::Error::custom)?;
1104
1105 let name = parts
1106 .next()
1107 .ok_or_else(|| D::Error::custom("Missing event name"))?
1108 .parse::<String>()
1109 .map_err(D::Error::custom)?;
1110
1111 Ok(S3EventName { kind, name })
1112 }
1113}
1114
1115impl Serialize for S3EventName {
1116 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1117 where
1118 S: Serializer,
1119 {
1120 serializer.serialize_str(&format!("{}:{}", self.kind, self.name))
1121 }
1122}
1123
1124#[derive(Clone, Debug, Deserialize, Serialize)]
1125#[serde(rename_all = "camelCase")]
1126pub struct S3Message {
1127 pub bucket: S3Bucket,
1128 pub object: S3Object,
1129}
1130
1131#[derive(Clone, Debug, Deserialize, Serialize)]
1132#[serde(rename_all = "camelCase")]
1133pub struct S3Bucket {
1134 pub name: String,
1135}
1136
1137#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
1138#[serde(rename_all = "camelCase")]
1139pub struct S3Object {
1140 #[serde(with = "urlencoded_string")]
1143 pub key: String,
1144}
1145
1146mod urlencoded_string {
1147 use percent_encoding::{percent_decode, utf8_percent_encode};
1148
1149 pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
1150 where
1151 D: serde::de::Deserializer<'de>,
1152 {
1153 use serde::de::Error;
1154
1155 serde::de::Deserialize::deserialize(deserializer).and_then(|s: &[u8]| {
1156 let decoded = if s.contains(&b'+') {
1157 let s = s
1159 .iter()
1160 .map(|c| if *c == b'+' { b' ' } else { *c })
1161 .collect::<Vec<_>>();
1162 percent_decode(&s).decode_utf8().map(Into::into)
1163 } else {
1164 percent_decode(s).decode_utf8().map(Into::into)
1165 };
1166
1167 decoded
1168 .map_err(|err| D::Error::custom(format!("error url decoding S3 object key: {err}")))
1169 })
1170 }
1171
1172 pub fn serialize<S>(s: &str, serializer: S) -> Result<S::Ok, S::Error>
1173 where
1174 S: serde::ser::Serializer,
1175 {
1176 serializer.serialize_str(
1177 &utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).collect::<String>(),
1178 )
1179 }
1180}
1181
1182#[test]
1183fn test_key_deserialize() {
1184 let value = serde_json::from_str(r#"{"key": "noog+nork"}"#).unwrap();
1185 assert_eq!(
1186 S3Object {
1187 key: "noog nork".to_string(),
1188 },
1189 value
1190 );
1191
1192 let value = serde_json::from_str(r#"{"key": "noog%2bnork"}"#).unwrap();
1193 assert_eq!(
1194 S3Object {
1195 key: "noog+nork".to_string(),
1196 },
1197 value
1198 );
1199}
1200
1201#[test]
1202fn test_s3_testevent() {
1203 let value: S3TestEvent = serde_json::from_str(
1204 r#"{
1205 "Service":"Amazon S3",
1206 "Event":"s3:TestEvent",
1207 "Time":"2014-10-13T15:57:02.089Z",
1208 "Bucket":"bucketname",
1209 "RequestId":"5582815E1AEA5ADF",
1210 "HostId":"8cLeGAmw098X5cv4Zkwcmo8vvZa3eH3eKxsPzbB9wrR+YstdA6Knx4Ip8EXAMPLE"
1211 }"#,
1212 )
1213 .unwrap();
1214
1215 assert_eq!(value.service, "Amazon S3".to_string());
1216 assert_eq!(value.bucket, "bucketname".to_string());
1217 assert_eq!(value.event.kind, "s3".to_string());
1218 assert_eq!(value.event.name, "TestEvent".to_string());
1219}
1220
1221#[test]
1222fn test_s3_sns_testevent() {
1223 let sns_value: SnsNotification = serde_json::from_str(
1224 r#"{
1225 "Type" : "Notification",
1226 "MessageId" : "63a3f6b6-d533-4a47-aef9-fcf5cf758c76",
1227 "TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
1228 "Subject" : "Testing publish to subscribed queues",
1229 "Message" : "{\"Bucket\":\"bucketname\",\"Event\":\"s3:TestEvent\",\"HostId\":\"8cLeGAmw098X5cv4Zkwcmo8vvZa3eH3eKxsPzbB9wrR+YstdA6Knx4Ip8EXAMPLE\",\"RequestId\":\"5582815E1AEA5ADF\",\"Service\":\"Amazon S3\",\"Time\":\"2014-10-13T15:57:02.089Z\"}",
1230 "Timestamp" : "2012-03-29T05:12:16.901Z",
1231 "SignatureVersion" : "1",
1232 "Signature" : "EXAMPLEnTrFPa3...",
1233 "SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
1234 "UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:123456789012:MyTopic:c7fe3a54-ab0e-4ec2-88e0-db410a0f2bee"
1235 }"#,
1236 ).unwrap();
1237
1238 assert_eq!(
1239 sns_value.timestamp,
1240 DateTime::parse_from_rfc3339("2012-03-29T05:12:16.901Z")
1241 .unwrap()
1242 .to_utc()
1243 );
1244
1245 let value: S3TestEvent = serde_json::from_str(sns_value.message.as_ref()).unwrap();
1246
1247 assert_eq!(value.service, "Amazon S3".to_string());
1248 assert_eq!(value.bucket, "bucketname".to_string());
1249 assert_eq!(value.event.kind, "s3".to_string());
1250 assert_eq!(value.event.name, "TestEvent".to_string());
1251}
1252
1253#[test]
1254fn parse_sqs_config() {
1255 let config: Config = serde_yaml::from_str(
1256 r#"queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1257"#,
1258 )
1259 .unwrap();
1260 assert_eq!(
1261 config.queue_url,
1262 "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1263 );
1264 assert!(config.deferred.is_none());
1265
1266 let config: Config = serde_yaml::from_str(indoc::indoc! {r#"
1267 queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1268 deferred:
1269 queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyDeferredQueue"
1270 max_age_secs: 3600
1271 "#})
1272 .unwrap();
1273 assert_eq!(
1274 config.queue_url,
1275 "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1276 );
1277 let Some(deferred) = config.deferred else {
1278 panic!("Expected deferred config");
1279 };
1280 assert_eq!(
1281 deferred.queue_url,
1282 "https://sqs.us-east-1.amazonaws.com/123456789012/MyDeferredQueue"
1283 );
1284 assert_eq!(deferred.max_age_secs, 3600);
1285
1286 let test: Result<Config, serde_yaml::Error> = serde_yaml::from_str(indoc::indoc! {r#"
1287 queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1288 deferred:
1289 max_age_secs: 3600
1290 "#});
1291 assert!(test.is_err());
1292
1293 let test: Result<Config, serde_yaml::Error> = serde_yaml::from_str(indoc::indoc! {r#"
1294 queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1295 deferred:
1296 queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyDeferredQueue"
1297 "#});
1298 assert!(test.is_err());
1299}