From 0579339401b3a058bd89566b770737d1fe5ed61c Mon Sep 17 00:00:00 2001 From: alexgallotta <5581237+alexgallotta@users.noreply.github.com> Date: Wed, 6 Nov 2024 12:51:10 -0500 Subject: [PATCH 01/15] add eventbridge event --- .../src/lifecycle/invocation/span_inferrer.rs | 25 +++ .../invocation/triggers/event_bridge_event.rs | 168 ++++++++++++++++++ .../src/lifecycle/invocation/triggers/mod.rs | 1 + bottlecap/tests/payloads/event_bridge.json | 19 ++ .../tests/payloads/event_bridge_span.json | 17 ++ 5 files changed, 230 insertions(+) create mode 100644 bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs create mode 100644 bottlecap/tests/payloads/event_bridge.json create mode 100644 bottlecap/tests/payloads/event_bridge_span.json diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index 4ad90dffa..e2c2a5437 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -7,6 +7,7 @@ use tracing::debug; use crate::config::AwsConfig; +use crate::lifecycle::invocation::triggers::event_bridge_event::EventBridgeEvent; use crate::lifecycle::invocation::triggers::{ api_gateway_http_event::APIGatewayHttpEvent, api_gateway_rest_event::APIGatewayRestEvent, sqs_event::SqsRecord, Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG, @@ -104,6 +105,30 @@ impl SpanInferrer { self.is_async_span = t.is_async(); self.inferred_span = Some(span); } + } else if EventBridgeEvent::is_match(payload_value) { + if let Some(t) = EventBridgeEvent::new(payload_value.clone()) { + let mut span = Span { + span_id: Self::generate_span_id(), + ..Default::default() + }; + + t.enrich_span(&mut span); + span.meta.extend([ + ( + FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), + "eventbridge".to_string(), + ), + ( + FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(), + t.get_arn(&aws_config.region), + ), + ]); + + self.carrier = Some(t.get_carrier()); + self.trigger_tags = Some(t.get_tags()); + self.is_async_span = t.is_async(); + self.inferred_span = Some(span); + } } else { debug!("Unable to infer span from payload: no matching trigger found"); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs new file mode 100644 index 000000000..d45f549ab --- /dev/null +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -0,0 +1,168 @@ +use chrono::{DateTime, Utc}; +use datadog_trace_protobuf::pb::Span; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use tracing::debug; + +use crate::lifecycle::invocation::{processor::MS_TO_NS, triggers::Trigger}; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct EventBridgeEvent { + pub id: String, + pub version: String, + pub account: String, + pub time: String, + pub region: String, + pub resources: Vec, + pub source: String, + #[serde(rename = "detail-type")] + pub detail_type: String, + pub detail: Value, + #[serde(rename = "replay-name")] + pub replay_name: Option, +} + +impl Trigger for EventBridgeEvent { + fn new(payload: Value) -> Option { + match serde_json::from_value(payload) { + Ok(event) => Some(event), + Err(e) => { + debug!("Failed to deserialize EventBridgeEvent: {}", e); + None + } + } + } + + fn is_match(payload: &Value) -> bool { + payload.get("detail-type").is_some() + } + + fn enrich_span(&self, span: &mut Span) { + span.name = "aws.eventbridge".to_string(); + // TODO service name fallback value for now, needs service mapping + span.service = "eventbridge".to_string(); + span.resource.clone_from(&self.source); + span.r#type = "web".to_string(); + + let parsed_date: DateTime = self.time.parse().expect("Failed to parse date"); + let start_time = parsed_date.timestamp_millis() as f64 * MS_TO_NS; + span.start = start_time as i64; + span.meta.extend(HashMap::from([ + ("operation_name".to_string(), "aws.eventbridge".to_string()), + ("resource_names".to_string(), self.source.clone()), + ("detail_type".to_string(), self.detail_type.clone()), + ])); + } + + fn get_tags(&self) -> HashMap { + // the only 2 trigger tags seems to be function_trigger.event_source and + // function_trigger.event_source_arn and they are added in the trigger + HashMap::new() + } + + fn get_arn(&self, _region: &str) -> String { + // TODO not sure what the ARN should be for EventBridge, go-agent is using source + self.source.clone() + } + + fn get_carrier(&self) -> HashMap { + if let Ok(detail) = serde_json::from_value::>(self.detail.clone()) { + if let Some(datadog) = detail.get("_datadog") { + if let Ok(datadog_map) = + serde_json::from_value::>(datadog.clone()) + { + return datadog_map; + } + } + } + HashMap::new() + } + + fn is_async(&self) -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lifecycle::invocation::triggers::test_utils::read_json_file; + + #[test] + fn test_new() { + let json = read_json_file("event_bridge.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let result = + EventBridgeEvent::new(payload).expect("Failed to deserialize into EventBridgeEvent"); + + let expected = EventBridgeEvent { + id: "bd3c8258-8d30-007c-2562-64715b2d0ea8".to_string(), + version: "0".to_string(), + account: "601427279990".to_string(), + time: "2022-01-24T16:00:10Z".to_string(), + region: "eu-west-1".to_string(), + resources: vec![], + source: "my.event".to_string(), + detail_type: "UserSignUp".to_string(), + detail: serde_json::json!({ + "hello": "there", + "_datadog": { + "x-datadog-trace-id": "5827606813695714842", + "x-datadog-parent-id": "4726693487091824375", + "x-datadog-sampled": "1", + "x-datadog-sampling-priority": "1" + } + }), + replay_name: None, + }; + + assert_eq!(result, expected); + } + + #[test] + fn test_is_match() { + let json = read_json_file("event_bridge.json"); + let payload = + serde_json::from_str(&json).expect("Failed to deserialize APIGatewayRestEvent"); + + assert!(EventBridgeEvent::is_match(&payload)); + } + + #[test] + fn test_is_not_match() { + let json = read_json_file("api_gateway_http_event.json"); + let payload = + serde_json::from_str(&json).expect("Failed to deserialize APIGatewayRestEvent"); + assert!(!EventBridgeEvent::is_match(&payload)); + } + + #[test] + fn test_enrich_span() { + let json = read_json_file("event_bridge.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + EventBridgeEvent::new(payload).expect("Failed to deserialize into EventBridgeEvent"); + + let mut span = Span::default(); + event.enrich_span(&mut span); + + let expected_span = serde_json::from_str(&read_json_file("event_bridge_span.json")) + .expect("Failed to deserialize into Span"); + assert_eq!(span, expected_span); + } + + #[test] + fn test_enrich_parameterized_span() { + //TODO + } + + #[test] + fn test_get_arn() { + let json = read_json_file("event_bridge.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let arn = + EventBridgeEvent::new(payload).expect("Failed to deserialize EventBridgeEvent").get_arn("don't care"); + assert_eq!(arn, "my.event"); + } +} diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index f65a9155a..bacd59a58 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -6,6 +6,7 @@ use serde_json::Value; pub mod api_gateway_http_event; pub mod api_gateway_rest_event; +pub mod event_bridge_event; pub mod sqs_event; pub const DATADOG_CARRIER_KEY: &str = "_datadog"; diff --git a/bottlecap/tests/payloads/event_bridge.json b/bottlecap/tests/payloads/event_bridge.json new file mode 100644 index 000000000..61860abbe --- /dev/null +++ b/bottlecap/tests/payloads/event_bridge.json @@ -0,0 +1,19 @@ +{ + "version": "0", + "id": "bd3c8258-8d30-007c-2562-64715b2d0ea8", + "detail-type": "UserSignUp", + "source": "my.event", + "account": "601427279990", + "time": "2022-01-24T16:00:10Z", + "region": "eu-west-1", + "resources": [], + "detail": { + "hello": "there", + "_datadog": { + "x-datadog-trace-id": "5827606813695714842", + "x-datadog-parent-id": "4726693487091824375", + "x-datadog-sampled": "1", + "x-datadog-sampling-priority": "1" + } + } +} diff --git a/bottlecap/tests/payloads/event_bridge_span.json b/bottlecap/tests/payloads/event_bridge_span.json new file mode 100644 index 000000000..288ff1219 --- /dev/null +++ b/bottlecap/tests/payloads/event_bridge_span.json @@ -0,0 +1,17 @@ +{ + "service": "eventbridge", + "name": "aws.eventbridge", + "resource": "my.event", + "trace_id": 0, + "span_id": 0, + "parent_id": 0, + "start": 1643040010000000000, + "duration": 0, + "meta": { + "resource_names": "my.event", + "operation_name": "aws.eventbridge", + "detail_type": "UserSignUp" + }, + "metrics": {}, + "type": "web" +} \ No newline at end of file From 4c811e8200f4fd2f5fbfed32d41e6a6cfa360867 Mon Sep 17 00:00:00 2001 From: alexgallotta <5581237+alexgallotta@users.noreply.github.com> Date: Wed, 6 Nov 2024 13:54:05 -0500 Subject: [PATCH 02/15] fix test path --- .../src/lifecycle/invocation/triggers/event_bridge_event.rs | 6 ++++-- bottlecap/src/lifecycle/invocation/triggers/mod.rs | 6 +++++- bottlecap/src/proc/mod.rs | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index d45f549ab..aff3ada5e 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -38,6 +38,7 @@ impl Trigger for EventBridgeEvent { payload.get("detail-type").is_some() } + #[allow(clippy::cast_possible_truncation)] fn enrich_span(&self, span: &mut Span) { span.name = "aws.eventbridge".to_string(); // TODO service name fallback value for now, needs service mapping @@ -161,8 +162,9 @@ mod tests { fn test_get_arn() { let json = read_json_file("event_bridge.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let arn = - EventBridgeEvent::new(payload).expect("Failed to deserialize EventBridgeEvent").get_arn("don't care"); + let arn = EventBridgeEvent::new(payload) + .expect("Failed to deserialize EventBridgeEvent") + .get_arn("don't care"); assert_eq!(arn, "my.event"); } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index bacd59a58..da89fb8fd 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -52,9 +52,13 @@ where #[cfg(test)] pub mod test_utils { use std::fs; + use std::path::PathBuf; #[must_use] pub fn read_json_file(file_name: &str) -> String { - fs::read_to_string(format!("tests/payloads/{file_name}")).expect("Failed to read file") + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("tests/payloads"); + path.push(file_name); + fs::read_to_string(path).expect("Failed to read file") } } diff --git a/bottlecap/src/proc/mod.rs b/bottlecap/src/proc/mod.rs index 23d6680ec..6a1613417 100644 --- a/bottlecap/src/proc/mod.rs +++ b/bottlecap/src/proc/mod.rs @@ -184,6 +184,7 @@ fn get_uptime_from_path(path: &str) -> Result { #[allow(clippy::unwrap_used)] mod tests { use super::*; + use std::path::PathBuf; #[test] #[allow(clippy::float_cmp)] From c8be100c8915777febfbbb72dd377f986da8aedb Mon Sep 17 00:00:00 2001 From: alexgallotta <5581237+alexgallotta@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:10:46 -0500 Subject: [PATCH 03/15] add comments with code ref and fix metadata api-gateway --- .../src/lifecycle/invocation/triggers/event_bridge_event.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index aff3ada5e..0b710eb95 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -59,11 +59,13 @@ impl Trigger for EventBridgeEvent { fn get_tags(&self) -> HashMap { // the only 2 trigger tags seems to be function_trigger.event_source and // function_trigger.event_source_arn and they are added in the trigger + // https://github.com/DataDog/datadog-agent/blob/main/pkg/serverless/invocationlifecycle/init.go#L114-L115 HashMap::new() } fn get_arn(&self, _region: &str) -> String { // TODO not sure what the ARN should be for EventBridge, go-agent is using source + // https://github.com/DataDog/datadog-agent/blob/main/pkg/serverless/invocationlifecycle/init.go#L115 self.source.clone() } From 13d5b5073e1008d2a56393ac773141d0b0170dd2 Mon Sep 17 00:00:00 2001 From: alexgallotta <5581237+alexgallotta@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:25:04 -0500 Subject: [PATCH 04/15] fix error message --- .../src/lifecycle/invocation/triggers/event_bridge_event.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index 0b710eb95..06c52dbf5 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -126,8 +126,7 @@ mod tests { #[test] fn test_is_match() { let json = read_json_file("event_bridge.json"); - let payload = - serde_json::from_str(&json).expect("Failed to deserialize APIGatewayRestEvent"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize EventBridgeEvent"); assert!(EventBridgeEvent::is_match(&payload)); } @@ -135,8 +134,7 @@ mod tests { #[test] fn test_is_not_match() { let json = read_json_file("api_gateway_http_event.json"); - let payload = - serde_json::from_str(&json).expect("Failed to deserialize APIGatewayRestEvent"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize EventBridgeEvent"); assert!(!EventBridgeEvent::is_match(&payload)); } From 3fd3b7c8a9dc3b15140f0e98999ceae3b96fc0ca Mon Sep 17 00:00:00 2001 From: alexgallotta <5581237+alexgallotta@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:41:41 -0500 Subject: [PATCH 05/15] clean import --- bottlecap/src/lifecycle/invocation/span_inferrer.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index e2c2a5437..d1636b1fc 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -7,10 +7,9 @@ use tracing::debug; use crate::config::AwsConfig; -use crate::lifecycle::invocation::triggers::event_bridge_event::EventBridgeEvent; use crate::lifecycle::invocation::triggers::{ api_gateway_http_event::APIGatewayHttpEvent, api_gateway_rest_event::APIGatewayRestEvent, - sqs_event::SqsRecord, Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG, + event_bridge_event::EventBridgeEvent,sqs_event::SqsRecord, Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG, }; use crate::tags::lambda::tags::{INIT_TYPE, SNAP_START_VALUE}; From 375a2162b30cce405479b604a826cd471a79bff1 Mon Sep 17 00:00:00 2001 From: alexgallotta <5581237+alexgallotta@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:44:21 -0500 Subject: [PATCH 06/15] make build faster using host network --- scripts/build_bottlecap_layer.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/build_bottlecap_layer.sh b/scripts/build_bottlecap_layer.sh index cf8cd0d22..8f66b39b0 100755 --- a/scripts/build_bottlecap_layer.sh +++ b/scripts/build_bottlecap_layer.sh @@ -40,6 +40,7 @@ _docker_build_bottlecap_zip() { docker buildx build --platform linux/${arch} \ -t datadog/build-bottlecap-${arch} \ -f ./scripts/Dockerfile.bottlecap.dev \ + --network=host \ --build-arg PLATFORM=$PLATFORM \ . -o $TARGET_DIR/datadog_bottlecap-${arch} From 4d88fbd7eaae7249c9c9880507b136854ad9699b Mon Sep 17 00:00:00 2001 From: alexgallotta <5581237+alexgallotta@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:01:56 -0500 Subject: [PATCH 07/15] fix conflicts and tests --- bottlecap/src/lifecycle/invocation/span_inferrer.rs | 3 ++- bottlecap/src/proc/mod.rs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index d1636b1fc..eb4cf28df 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -9,7 +9,8 @@ use crate::config::AwsConfig; use crate::lifecycle::invocation::triggers::{ api_gateway_http_event::APIGatewayHttpEvent, api_gateway_rest_event::APIGatewayRestEvent, - event_bridge_event::EventBridgeEvent,sqs_event::SqsRecord, Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG, + event_bridge_event::EventBridgeEvent, sqs_event::SqsRecord, Trigger, + FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG, FUNCTION_TRIGGER_EVENT_SOURCE_TAG, }; use crate::tags::lambda::tags::{INIT_TYPE, SNAP_START_VALUE}; diff --git a/bottlecap/src/proc/mod.rs b/bottlecap/src/proc/mod.rs index 6a1613417..48c7c1964 100644 --- a/bottlecap/src/proc/mod.rs +++ b/bottlecap/src/proc/mod.rs @@ -186,6 +186,12 @@ mod tests { use super::*; use std::path::PathBuf; + fn path_from_root(file: &str) -> String { + let mut safe_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + safe_path.push(file); + safe_path.to_str().unwrap().to_string() + } + #[test] #[allow(clippy::float_cmp)] fn test_get_network_data() { From 5e6992b82158863d488c635dfb0ed37f71e5c62a Mon Sep 17 00:00:00 2001 From: alexgallotta <5581237+alexgallotta@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:05:11 -0500 Subject: [PATCH 08/15] fix test conflicts --- bottlecap/src/proc/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bottlecap/src/proc/mod.rs b/bottlecap/src/proc/mod.rs index 48c7c1964..3dfa1a67f 100644 --- a/bottlecap/src/proc/mod.rs +++ b/bottlecap/src/proc/mod.rs @@ -196,7 +196,7 @@ mod tests { #[allow(clippy::float_cmp)] fn test_get_network_data() { let path = "./tests/proc/net/valid_dev"; - let network_data_result = get_network_data_from_path(path); + let network_data_result = get_network_data_from_path(path_from_root(path).as_str()); assert!(network_data_result.is_ok()); let network_data_result = network_data_result.unwrap(); assert_eq!(network_data_result.rx_bytes, 180.0); @@ -223,7 +223,7 @@ mod tests { #[allow(clippy::float_cmp)] fn test_get_cpu_data() { let path = "./tests/proc/stat/valid_stat"; - let cpu_data_result = get_cpu_data_from_path(path); + let cpu_data_result = get_cpu_data_from_path(path_from_root(path).as_str()); assert!(cpu_data_result.is_ok()); let cpu_data = cpu_data_result.unwrap(); assert_eq!(cpu_data.total_user_time_ms, 23370.0); @@ -274,7 +274,7 @@ mod tests { #[allow(clippy::float_cmp)] fn test_get_uptime_data() { let path = "./tests/proc/uptime/valid_uptime"; - let uptime_data_result = get_uptime_from_path(path); + let uptime_data_result = get_uptime_from_path(path_from_root(path).as_str()); assert!(uptime_data_result.is_ok()); let uptime_data = uptime_data_result.unwrap(); assert_eq!(uptime_data, 3_213_103_123_000.0); From 6ab14505e0c00b97b926f4164d4f02551c9974d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Fri, 8 Nov 2024 21:37:36 -0500 Subject: [PATCH 09/15] resolve merge conflicts --- .../invocation/triggers/event_bridge_event.rs | 13 ++++++++----- bottlecap/src/lifecycle/invocation/triggers/mod.rs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index 06c52dbf5..ea43a3eb4 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -5,7 +5,10 @@ use serde_json::Value; use std::collections::HashMap; use tracing::debug; -use crate::lifecycle::invocation::{processor::MS_TO_NS, triggers::Trigger}; +use crate::lifecycle::invocation::{ + processor::MS_TO_NS, + triggers::{Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_TAG}, +}; #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct EventBridgeEvent { @@ -57,10 +60,10 @@ impl Trigger for EventBridgeEvent { } fn get_tags(&self) -> HashMap { - // the only 2 trigger tags seems to be function_trigger.event_source and - // function_trigger.event_source_arn and they are added in the trigger - // https://github.com/DataDog/datadog-agent/blob/main/pkg/serverless/invocationlifecycle/init.go#L114-L115 - HashMap::new() + HashMap::from([( + FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), + "eventbridge".to_string(), + )]) } fn get_arn(&self, _region: &str) -> String { diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index 4b1841412..e0d347f08 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -7,8 +7,8 @@ use serde_json::Value; pub mod api_gateway_http_event; pub mod api_gateway_rest_event; -pub mod event_bridge_event; pub mod dynamodb_event; +pub mod event_bridge_event; pub mod s3_event; pub mod sns_event; pub mod sqs_event; From 129590d0bef13590784d706dc4510fe6c4915aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:01:16 -0500 Subject: [PATCH 10/15] minor changes --- .../invocation/triggers/event_bridge_event.rs | 67 ++++++++++--------- ...ent_bridge.json => eventbridge_event.json} | 0 ...bridge_span.json => eventbridge_span.json} | 1 - 3 files changed, 35 insertions(+), 33 deletions(-) rename bottlecap/tests/payloads/{event_bridge.json => eventbridge_event.json} (100%) rename bottlecap/tests/payloads/{event_bridge_span.json => eventbridge_span.json} (90%) diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index ea43a3eb4..a832bb36d 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -6,8 +6,8 @@ use std::collections::HashMap; use tracing::debug; use crate::lifecycle::invocation::{ - processor::MS_TO_NS, - triggers::{Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_TAG}, + processor::S_TO_NS, + triggers::{Trigger, DATADOG_CARRIER_KEY, FUNCTION_TRIGGER_EVENT_SOURCE_TAG}, }; #[derive(Serialize, Deserialize, Debug, PartialEq)] @@ -15,7 +15,7 @@ pub struct EventBridgeEvent { pub id: String, pub version: String, pub account: String, - pub time: String, + pub time: DateTime, pub region: String, pub resources: Vec, pub source: String, @@ -31,7 +31,7 @@ impl Trigger for EventBridgeEvent { match serde_json::from_value(payload) { Ok(event) => Some(event), Err(e) => { - debug!("Failed to deserialize EventBridgeEvent: {}", e); + debug!("Failed to deserialize EventBridge Event: {}", e); None } } @@ -39,22 +39,30 @@ impl Trigger for EventBridgeEvent { fn is_match(payload: &Value) -> bool { payload.get("detail-type").is_some() + && payload + .get("source") + .and_then(Value::as_str) + .map_or(false, |s| s != "aws.events") } #[allow(clippy::cast_possible_truncation)] fn enrich_span(&self, span: &mut Span) { - span.name = "aws.eventbridge".to_string(); - // TODO service name fallback value for now, needs service mapping - span.service = "eventbridge".to_string(); - span.resource.clone_from(&self.source); - span.r#type = "web".to_string(); + // EventBridge events have a timestamp resolution in seconds + let start_time = self + .time + .timestamp_nanos_opt() + .unwrap_or((self.time.timestamp_millis() as f64 * S_TO_NS) as i64); + + // todo: service mapping and peer service + let service_name = "eventbridge"; - let parsed_date: DateTime = self.time.parse().expect("Failed to parse date"); - let start_time = parsed_date.timestamp_millis() as f64 * MS_TO_NS; - span.start = start_time as i64; + span.name = String::from("aws.eventbridge"); + span.service = service_name.to_string(); + span.resource.clone_from(&self.source); + span.r#type = String::from("web"); + span.start = start_time; span.meta.extend(HashMap::from([ ("operation_name".to_string(), "aws.eventbridge".to_string()), - ("resource_names".to_string(), self.source.clone()), ("detail_type".to_string(), self.detail_type.clone()), ])); } @@ -67,19 +75,14 @@ impl Trigger for EventBridgeEvent { } fn get_arn(&self, _region: &str) -> String { - // TODO not sure what the ARN should be for EventBridge, go-agent is using source - // https://github.com/DataDog/datadog-agent/blob/main/pkg/serverless/invocationlifecycle/init.go#L115 self.source.clone() } fn get_carrier(&self) -> HashMap { if let Ok(detail) = serde_json::from_value::>(self.detail.clone()) { - if let Some(datadog) = detail.get("_datadog") { - if let Ok(datadog_map) = - serde_json::from_value::>(datadog.clone()) - { - return datadog_map; - } + if let Some(carrier) = detail.get(DATADOG_CARRIER_KEY) { + return serde_json::from_value::>(carrier.clone()) + .unwrap_or_default(); } } HashMap::new() @@ -97,7 +100,7 @@ mod tests { #[test] fn test_new() { - let json = read_json_file("event_bridge.json"); + let json = read_json_file("eventbridge_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let result = EventBridgeEvent::new(payload).expect("Failed to deserialize into EventBridgeEvent"); @@ -106,7 +109,9 @@ mod tests { id: "bd3c8258-8d30-007c-2562-64715b2d0ea8".to_string(), version: "0".to_string(), account: "601427279990".to_string(), - time: "2022-01-24T16:00:10Z".to_string(), + time: DateTime::parse_from_rfc3339("2022-01-24T16:00:10Z") + .expect("Failed to parse time") + .with_timezone(&Utc), region: "eu-west-1".to_string(), resources: vec![], source: "my.event".to_string(), @@ -128,7 +133,7 @@ mod tests { #[test] fn test_is_match() { - let json = read_json_file("event_bridge.json"); + let json = read_json_file("eventbridge_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize EventBridgeEvent"); assert!(EventBridgeEvent::is_match(&payload)); @@ -143,7 +148,7 @@ mod tests { #[test] fn test_enrich_span() { - let json = read_json_file("event_bridge.json"); + let json = read_json_file("eventbridge_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = EventBridgeEvent::new(payload).expect("Failed to deserialize into EventBridgeEvent"); @@ -151,9 +156,9 @@ mod tests { let mut span = Span::default(); event.enrich_span(&mut span); - let expected_span = serde_json::from_str(&read_json_file("event_bridge_span.json")) + let expected = serde_json::from_str(&read_json_file("eventbridge_span.json")) .expect("Failed to deserialize into Span"); - assert_eq!(span, expected_span); + assert_eq!(span, expected); } #[test] @@ -163,11 +168,9 @@ mod tests { #[test] fn test_get_arn() { - let json = read_json_file("event_bridge.json"); + let json = read_json_file("eventbridge_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let arn = EventBridgeEvent::new(payload) - .expect("Failed to deserialize EventBridgeEvent") - .get_arn("don't care"); - assert_eq!(arn, "my.event"); + let event = EventBridgeEvent::new(payload).expect("Failed to deserialize EventBridgeEvent"); + assert_eq!(event.get_arn("us-east-1"), "my.event"); } } diff --git a/bottlecap/tests/payloads/event_bridge.json b/bottlecap/tests/payloads/eventbridge_event.json similarity index 100% rename from bottlecap/tests/payloads/event_bridge.json rename to bottlecap/tests/payloads/eventbridge_event.json diff --git a/bottlecap/tests/payloads/event_bridge_span.json b/bottlecap/tests/payloads/eventbridge_span.json similarity index 90% rename from bottlecap/tests/payloads/event_bridge_span.json rename to bottlecap/tests/payloads/eventbridge_span.json index 288ff1219..255a4c9b9 100644 --- a/bottlecap/tests/payloads/event_bridge_span.json +++ b/bottlecap/tests/payloads/eventbridge_span.json @@ -8,7 +8,6 @@ "start": 1643040010000000000, "duration": 0, "meta": { - "resource_names": "my.event", "operation_name": "aws.eventbridge", "detail_type": "UserSignUp" }, From e95ed81debd5dd6ebd76cb725c2122947ea4c955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:17:45 -0500 Subject: [PATCH 11/15] add missing unit test --- .../invocation/triggers/event_bridge_event.rs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index a832bb36d..dcf893b83 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -81,8 +81,7 @@ impl Trigger for EventBridgeEvent { fn get_carrier(&self) -> HashMap { if let Ok(detail) = serde_json::from_value::>(self.detail.clone()) { if let Some(carrier) = detail.get(DATADOG_CARRIER_KEY) { - return serde_json::from_value::>(carrier.clone()) - .unwrap_or_default(); + return serde_json::from_value(carrier.clone()).unwrap_or_default(); } } HashMap::new() @@ -161,11 +160,6 @@ mod tests { assert_eq!(span, expected); } - #[test] - fn test_enrich_parameterized_span() { - //TODO - } - #[test] fn test_get_arn() { let json = read_json_file("eventbridge_event.json"); @@ -173,4 +167,28 @@ mod tests { let event = EventBridgeEvent::new(payload).expect("Failed to deserialize EventBridgeEvent"); assert_eq!(event.get_arn("us-east-1"), "my.event"); } + + #[test] + fn test_get_carrier() { + let json = read_json_file("eventbridge_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + EventBridgeEvent::new(payload).expect("Failed to deserialize EventBridge Event"); + let carrier = event.get_carrier(); + + let expected = HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + "5827606813695714842".to_string(), + ), + ( + "x-datadog-parent-id".to_string(), + "4726693487091824375".to_string(), + ), + ("x-datadog-sampling-priority".to_string(), "1".to_string()), + ("x-datadog-sampled".to_string(), "1".to_string()), + ]); + + assert_eq!(carrier, expected); + } } From a65df82890900ebdab86ddbb8ef834bd089a99f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:29:35 -0500 Subject: [PATCH 12/15] update events for testing --- .../tests/payloads/eventbridge_event.json | 6 ++++-- .../eventbridge_no_resource_name_event.json | 19 +++++++++++++++++++ .../eventbridge_no_timestamp_event.json | 19 +++++++++++++++++++ .../tests/payloads/eventbridge_span.json | 4 ++-- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 bottlecap/tests/payloads/eventbridge_no_resource_name_event.json create mode 100644 bottlecap/tests/payloads/eventbridge_no_timestamp_event.json diff --git a/bottlecap/tests/payloads/eventbridge_event.json b/bottlecap/tests/payloads/eventbridge_event.json index 61860abbe..8c9d91d9e 100644 --- a/bottlecap/tests/payloads/eventbridge_event.json +++ b/bottlecap/tests/payloads/eventbridge_event.json @@ -4,7 +4,7 @@ "detail-type": "UserSignUp", "source": "my.event", "account": "601427279990", - "time": "2022-01-24T16:00:10Z", + "time": "2024-11-09T08:22:15Z", "region": "eu-west-1", "resources": [], "detail": { @@ -13,7 +13,9 @@ "x-datadog-trace-id": "5827606813695714842", "x-datadog-parent-id": "4726693487091824375", "x-datadog-sampled": "1", - "x-datadog-sampling-priority": "1" + "x-datadog-sampling-priority": "1", + "x-datadog-resource-name": "testBus", + "x-datadog-start-time": "1731183820135" } } } diff --git a/bottlecap/tests/payloads/eventbridge_no_resource_name_event.json b/bottlecap/tests/payloads/eventbridge_no_resource_name_event.json new file mode 100644 index 000000000..778e40628 --- /dev/null +++ b/bottlecap/tests/payloads/eventbridge_no_resource_name_event.json @@ -0,0 +1,19 @@ +{ + "version": "0", + "id": "bd3c8258-8d30-007c-2562-64715b2d0ea8", + "detail-type": "UserSignUp", + "source": "my.event", + "account": "601427279990", + "time": "2024-11-09T08:22:15Z", + "region": "eu-west-1", + "resources": [], + "detail": { + "hello": "there", + "_datadog": { + "x-datadog-trace-id": "5827606813695714842", + "x-datadog-parent-id": "4726693487091824375", + "x-datadog-sampling-priority": "1", + "x-datadog-start-time": "1731183820135" + } + } +} diff --git a/bottlecap/tests/payloads/eventbridge_no_timestamp_event.json b/bottlecap/tests/payloads/eventbridge_no_timestamp_event.json new file mode 100644 index 000000000..d5e8d9c6c --- /dev/null +++ b/bottlecap/tests/payloads/eventbridge_no_timestamp_event.json @@ -0,0 +1,19 @@ +{ + "version": "0", + "id": "bd3c8258-8d30-007c-2562-64715b2d0ea8", + "detail-type": "UserSignUp", + "source": "my.event", + "account": "601427279990", + "time": "2024-11-09T08:22:15Z", + "region": "eu-west-1", + "resources": [], + "detail": { + "hello": "there", + "_datadog": { + "x-datadog-trace-id": "5827606813695714842", + "x-datadog-parent-id": "4726693487091824375", + "x-datadog-sampling-priority": "1", + "x-datadog-resource-name": "testBus" + } + } +} diff --git a/bottlecap/tests/payloads/eventbridge_span.json b/bottlecap/tests/payloads/eventbridge_span.json index 255a4c9b9..0515abd69 100644 --- a/bottlecap/tests/payloads/eventbridge_span.json +++ b/bottlecap/tests/payloads/eventbridge_span.json @@ -1,11 +1,11 @@ { "service": "eventbridge", "name": "aws.eventbridge", - "resource": "my.event", + "resource": "testBus", "trace_id": 0, "span_id": 0, "parent_id": 0, - "start": 1643040010000000000, + "start": 1731183820135000064, "duration": 0, "meta": { "operation_name": "aws.eventbridge", From bb3b6e8b50423089f22d99acb23b253a197c0882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:30:10 -0500 Subject: [PATCH 13/15] account for millisecond resolution and resource name --- .../invocation/triggers/event_bridge_event.rs | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index dcf893b83..a1c0570c2 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -6,10 +6,13 @@ use std::collections::HashMap; use tracing::debug; use crate::lifecycle::invocation::{ - processor::S_TO_NS, + processor::{MS_TO_NS, S_TO_NS}, triggers::{Trigger, DATADOG_CARRIER_KEY, FUNCTION_TRIGGER_EVENT_SOURCE_TAG}, }; +const DATADOG_START_TIME_KEY: &str = "x-datadog-start-time"; +const DATADOG_RESOURCE_NAME_KEY: &str = "x-datadog-resource-name"; + #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct EventBridgeEvent { pub id: String, @@ -48,17 +51,27 @@ impl Trigger for EventBridgeEvent { #[allow(clippy::cast_possible_truncation)] fn enrich_span(&self, span: &mut Span) { // EventBridge events have a timestamp resolution in seconds - let start_time = self + let start_time_seconds = self .time .timestamp_nanos_opt() .unwrap_or((self.time.timestamp_millis() as f64 * S_TO_NS) as i64); + let carrier = self.get_carrier(); + let resource_name = carrier + .get(DATADOG_RESOURCE_NAME_KEY) + .unwrap_or(&self.source) + .clone(); + let start_time = carrier + .get(DATADOG_START_TIME_KEY) + .and_then(|s| s.parse::().ok()) + .map_or(start_time_seconds, |s| (s * MS_TO_NS) as i64); + // todo: service mapping and peer service let service_name = "eventbridge"; span.name = String::from("aws.eventbridge"); span.service = service_name.to_string(); - span.resource.clone_from(&self.source); + span.resource = resource_name; span.r#type = String::from("web"); span.start = start_time; span.meta.extend(HashMap::from([ @@ -160,6 +173,34 @@ mod tests { assert_eq!(span, expected); } + #[test] + fn test_enrich_span_no_resource_name() { + let json = read_json_file("eventbridge_no_resource_name_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + EventBridgeEvent::new(payload).expect("Failed to deserialize into EventBridgeEvent"); + + let mut span = Span::default(); + event.enrich_span(&mut span); + + assert_eq!(span.resource, "my.event"); + } + + #[test] + fn test_enrich_span_no_timestamp() { + let json = read_json_file("eventbridge_no_timestamp_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + EventBridgeEvent::new(payload).expect("Failed to deserialize into EventBridgeEvent"); + + let mut span = Span::default(); + event.enrich_span(&mut span); + + assert_eq!(span.resource, "testBus"); + // Seconds resolution + assert_eq!(span.start, 1731140535000000000); + } + #[test] fn test_get_arn() { let json = read_json_file("eventbridge_event.json"); From f53aca8a2074fc4516db93628b9bba652a6e4b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Sun, 10 Nov 2024 21:24:42 -0500 Subject: [PATCH 14/15] fix unit tests --- .../invocation/triggers/event_bridge_event.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index a1c0570c2..f51b111c6 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -121,7 +121,7 @@ mod tests { id: "bd3c8258-8d30-007c-2562-64715b2d0ea8".to_string(), version: "0".to_string(), account: "601427279990".to_string(), - time: DateTime::parse_from_rfc3339("2022-01-24T16:00:10Z") + time: DateTime::parse_from_rfc3339("2024-11-09T08:22:15Z") .expect("Failed to parse time") .with_timezone(&Utc), region: "eu-west-1".to_string(), @@ -134,7 +134,9 @@ mod tests { "x-datadog-trace-id": "5827606813695714842", "x-datadog-parent-id": "4726693487091824375", "x-datadog-sampled": "1", - "x-datadog-sampling-priority": "1" + "x-datadog-sampling-priority": "1", + "x-datadog-resource-name": "testBus", + "x-datadog-start-time": "1731183820135" } }), replay_name: None, @@ -228,6 +230,11 @@ mod tests { ), ("x-datadog-sampling-priority".to_string(), "1".to_string()), ("x-datadog-sampled".to_string(), "1".to_string()), + ("x-datadog-resource-name".to_string(), "testBus".to_string()), + ( + "x-datadog-start-time".to_string(), + "1731183820135".to_string(), + ), ]); assert_eq!(carrier, expected); From 03c4199ccf192b1ee685f2361d1f165add375f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:42:06 -0500 Subject: [PATCH 15/15] remove `network` tag for runners --- scripts/build_bottlecap_layer.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/build_bottlecap_layer.sh b/scripts/build_bottlecap_layer.sh index 8f66b39b0..cf8cd0d22 100755 --- a/scripts/build_bottlecap_layer.sh +++ b/scripts/build_bottlecap_layer.sh @@ -40,7 +40,6 @@ _docker_build_bottlecap_zip() { docker buildx build --platform linux/${arch} \ -t datadog/build-bottlecap-${arch} \ -f ./scripts/Dockerfile.bottlecap.dev \ - --network=host \ --build-arg PLATFORM=$PLATFORM \ . -o $TARGET_DIR/datadog_bottlecap-${arch}