Skip to content

Commit 59c6d30

Browse files
committed
chore(agents): retrieve container tags hash from /info endpoint
1 parent 04394ec commit 59c6d30

3 files changed

Lines changed: 96 additions & 9 deletions

File tree

datadog-sidecar-ffi/src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,22 @@ pub unsafe extern "C" fn ddog_get_agent_info_env<'a>(
13421342
.unwrap_or(ffi::CharSlice::empty())
13431343
}
13441344

1345+
/// Gets the container tags hash from agent info (or empty if not existing)
1346+
#[no_mangle]
1347+
#[allow(clippy::missing_safety_doc)]
1348+
pub unsafe extern "C" fn ddog_get_agent_info_container_tags_hash<'a>(
1349+
reader: &'a mut AgentInfoReader,
1350+
changed: &mut bool,
1351+
) -> ffi::CharSlice<'a> {
1352+
let (has_changed, info) = reader.read();
1353+
*changed = has_changed;
1354+
1355+
info.as_ref()
1356+
.and_then(|i| i.container_tags_hash.as_ref())
1357+
.map(|s| ffi::CharSlice::from(s.as_str()))
1358+
.unwrap_or(ffi::CharSlice::empty())
1359+
}
1360+
13451361
#[macro_export]
13461362
macro_rules! check {
13471363
($failable:expr, $msg:expr) => {

libdd-data-pipeline/src/agent_info/fetcher.rs

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
//! Provides utilities to fetch the agent /info endpoint and an automatic fetcher to keep info
44
//! up-to-date
55
6-
use super::{schema::AgentInfo, AGENT_INFO_CACHE};
6+
use super::{
7+
schema::{AgentInfo, AgentInfoStruct},
8+
AGENT_INFO_CACHE,
9+
};
710
use anyhow::{anyhow, Result};
811
use http::header::HeaderName;
912
use http_body_util::BodyExt;
@@ -31,23 +34,37 @@ pub enum FetchInfoStatus {
3134
/// If the state hash is different from the current one:
3235
/// - Return a `FetchInfoStatus::NewState` of the info struct
3336
/// - Else return `FetchInfoStatus::SameState`
34-
pub async fn fetch_info_with_state(
37+
async fn fetch_info_with_state_and_container(
3538
info_endpoint: &Endpoint,
3639
current_state_hash: Option<&str>,
40+
current_container_tags_hash: Option<&str>,
3741
) -> Result<FetchInfoStatus> {
38-
let (new_state_hash, body_data) = fetch_and_hash_response(info_endpoint).await?;
42+
let (new_state_hash, body_data, container_tags_hash) =
43+
fetch_and_hash_response(info_endpoint).await?;
3944

40-
if current_state_hash.is_some_and(|state| state == new_state_hash) {
45+
if current_state_hash.is_some_and(|state| state == new_state_hash)
46+
&& current_container_tags_hash == container_tags_hash.as_deref()
47+
{
4148
return Ok(FetchInfoStatus::SameState);
4249
}
4350

51+
let mut info_struct: AgentInfoStruct = serde_json::from_slice(&body_data)?;
52+
info_struct.container_tags_hash = container_tags_hash;
53+
4454
let info = Box::new(AgentInfo {
4555
state_hash: new_state_hash,
46-
info: serde_json::from_slice(&body_data)?,
56+
info: info_struct,
4757
});
4858
Ok(FetchInfoStatus::NewState(info))
4959
}
5060

61+
pub async fn fetch_info_with_state(
62+
info_endpoint: &Endpoint,
63+
current_state_hash: Option<&str>,
64+
) -> Result<FetchInfoStatus> {
65+
fetch_info_with_state_and_container(info_endpoint, current_state_hash, None).await
66+
}
67+
5168
/// Fetch the info endpoint once and return the info.
5269
///
5370
/// Can be used for one-time access to the agent's info. If you need to access the info several
@@ -78,21 +95,30 @@ pub async fn fetch_info(info_endpoint: &Endpoint) -> Result<Box<AgentInfo>> {
7895

7996
/// Fetch and hash the response from the agent info endpoint.
8097
///
81-
/// Returns a tuple of (state_hash, response_body_bytes).
98+
/// Returns a tuple of (state_hash, response_body_bytes, container_tags_hash).
8299
/// The hash is calculated using SHA256 to match the agent's calculation method.
83-
async fn fetch_and_hash_response(info_endpoint: &Endpoint) -> Result<(String, bytes::Bytes)> {
100+
async fn fetch_and_hash_response(
101+
info_endpoint: &Endpoint,
102+
) -> Result<(String, bytes::Bytes, Option<String>)> {
84103
let req = info_endpoint
85104
.to_request_builder(concat!("Libdatadog/", env!("CARGO_PKG_VERSION")))?
86105
.method(http::Method::GET)
87106
.body(http_common::Body::empty());
88107
let client = http_common::new_default_client();
89108
let res = client.request(req?).await?;
90109

110+
// Extract the Datadog-Container-Tags-Hash header
111+
let container_tags_hash = res
112+
.headers()
113+
.get("Datadog-Container-Tags-Hash")
114+
.and_then(|v| v.to_str().ok())
115+
.map(|s| s.to_string());
116+
91117
let body_bytes = res.into_body().collect().await?;
92118
let body_data = body_bytes.to_bytes();
93119
let hash = format!("{:x}", Sha256::digest(&body_data));
94120

95-
Ok((hash, body_data))
121+
Ok((hash, body_data, container_tags_hash))
96122
}
97123

98124
/// Fetch the info endpoint and update an ArcSwap keeping it up-to-date.
@@ -224,7 +250,15 @@ impl AgentInfoFetcher {
224250
async fn fetch_and_update(&self) {
225251
let current_info = AGENT_INFO_CACHE.load();
226252
let current_hash = current_info.as_ref().map(|info| info.state_hash.as_str());
227-
let res = fetch_info_with_state(&self.info_endpoint, current_hash).await;
253+
let current_container_tags_hash = current_info
254+
.as_ref()
255+
.and_then(|info| info.info.container_tags_hash.as_deref());
256+
let res = fetch_info_with_state_and_container(
257+
&self.info_endpoint,
258+
current_hash,
259+
current_container_tags_hash,
260+
)
261+
.await;
228262
match res {
229263
Ok(FetchInfoStatus::NewState(new_info)) => {
230264
debug!("New /info state received");
@@ -406,6 +440,41 @@ mod single_threaded_tests {
406440
assert!(matches!(same_state_info_status, FetchInfoStatus::SameState));
407441
}
408442

443+
#[cfg_attr(miri, ignore)]
444+
#[tokio::test]
445+
async fn test_fetch_info_with_same_state_but_different_container_tags_hash() {
446+
let server = MockServer::start();
447+
let mock = server
448+
.mock_async(|when, then| {
449+
when.path("/info");
450+
then.status(200)
451+
.header("content-type", "application/json")
452+
.header("Datadog-Container-Tags-Hash", "new-container-hash")
453+
.body(TEST_INFO);
454+
})
455+
.await;
456+
let endpoint = Endpoint::from_url(server.url("/info").parse().unwrap());
457+
458+
let info_status = fetch_info_with_state_and_container(
459+
&endpoint,
460+
Some(TEST_INFO_HASH),
461+
Some("old-container-hash"),
462+
)
463+
.await
464+
.unwrap();
465+
466+
mock.assert();
467+
assert!(
468+
matches!(info_status, FetchInfoStatus::NewState(info) if *info == AgentInfo {
469+
state_hash: TEST_INFO_HASH.to_string(),
470+
info: AgentInfoStruct {
471+
container_tags_hash: Some("new-container-hash".to_string()),
472+
..serde_json::from_str(TEST_INFO).unwrap()
473+
},
474+
})
475+
);
476+
}
477+
409478
#[cfg_attr(miri, ignore)]
410479
#[tokio::test]
411480
async fn test_fetch_info() {

libdd-data-pipeline/src/agent_info/schema.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ pub struct AgentInfoStruct {
3535
pub peer_tags: Option<Vec<String>>,
3636
/// List of span kinds eligible for stats computation
3737
pub span_kinds_stats_computed: Option<Vec<String>>,
38+
/// Container tags hash from HTTP response header
39+
pub container_tags_hash: Option<String>,
3840
}
3941

4042
#[allow(missing_docs)]

0 commit comments

Comments
 (0)