datadog_agent_commons/ipc/
config.rs1use std::{path::PathBuf, time::Duration};
4
5use backon::{BackoffBuilder, ConstantBuilder};
6use saluki_config::GenericConfiguration;
7use saluki_error::{ErrorContext as _, GenericError};
8use serde::Deserialize;
9use tonic::transport::Uri;
10
11use crate::platform::PlatformSettings;
12
13fn default_agent_ipc_endpoint() -> Uri {
14 Uri::from_static("https://127.0.0.1:5001")
15}
16
17const fn default_connect_retry_attempts() -> usize {
18 10
19}
20
21const fn default_grpc_max_message_size() -> usize {
22 128 * 1024 * 1024
23}
24
25const fn default_connect_retry_backoff() -> Duration {
26 Duration::from_secs(2)
27}
28
29#[derive(Deserialize)]
31#[serde(default)]
32pub struct IpcAuthConfiguration {
33 auth_token_file_path: PathBuf,
40
41 ipc_cert_file_path: Option<PathBuf>,
50}
51
52impl IpcAuthConfiguration {
53 pub fn from_configuration(config: &GenericConfiguration) -> Result<Self, GenericError> {
59 config
60 .as_typed::<Self>()
61 .error_context("Failed to parse Datadog Agent IPC authentication configuration.")
62 }
63
64 pub fn auth_token_file_path(&self) -> PathBuf {
66 if self.auth_token_file_path.as_os_str().is_empty() {
67 return PlatformSettings::get_auth_token_path();
68 }
69
70 self.auth_token_file_path.clone()
71 }
72
73 pub fn ipc_cert_file_path(&self) -> PathBuf {
75 if let Some(path) = self.ipc_cert_file_path.as_ref() {
77 if !path.as_os_str().is_empty() {
78 return path.clone();
79 }
80 }
81
82 let auth_token_dir = if self.auth_token_file_path.as_os_str().is_empty() {
84 PlatformSettings::get_config_dir_path()
85 } else {
86 self.auth_token_file_path
87 .parent()
88 .unwrap_or(PlatformSettings::get_config_dir_path())
89 };
90
91 auth_token_dir.join(PlatformSettings::get_ipc_cert_filename())
92 }
93}
94
95impl Default for IpcAuthConfiguration {
96 fn default() -> Self {
97 Self {
98 auth_token_file_path: PlatformSettings::get_auth_token_path(),
99 ipc_cert_file_path: None,
100 }
101 }
102}
103
104#[derive(Deserialize)]
106pub struct RemoteAgentClientConfiguration {
107 #[serde(
117 rename = "agent_ipc_endpoint",
118 with = "http_serde_ext::uri",
119 default = "default_agent_ipc_endpoint"
120 )]
121 ipc_endpoint: Uri,
122
123 cmd_port: Option<u16>,
127
128 #[serde(flatten, default)]
130 auth: IpcAuthConfiguration,
131
132 #[serde(default = "default_connect_retry_attempts")]
136 connect_retry_attempts: usize,
137
138 #[serde(default = "default_connect_retry_backoff")]
142 connect_retry_backoff: Duration,
143
144 #[serde(
148 rename = "agent_ipc_grpc_max_message_size",
149 default = "default_grpc_max_message_size"
150 )]
151 grpc_max_message_size: usize,
152}
153
154impl RemoteAgentClientConfiguration {
155 pub fn from_configuration(config: &GenericConfiguration) -> Result<Self, GenericError> {
161 config
162 .as_typed::<Self>()
163 .error_context("Failed to parse Datadog Agent IPC client configuration.")
164 }
165
166 pub fn auth(&self) -> &IpcAuthConfiguration {
168 &self.auth
169 }
170
171 pub fn endpoint(&self) -> Result<Uri, GenericError> {
175 if let Some(cmd_port) = self.cmd_port {
176 format!("https://127.0.0.1:{}", cmd_port)
177 .parse::<Uri>()
178 .with_error_context(|| format!("failed to build URI from cmd_port {cmd_port}"))
179 } else {
180 Ok(self.ipc_endpoint.clone())
181 }
182 }
183
184 pub fn grpc_max_message_size(&self) -> usize {
186 self.grpc_max_message_size
187 }
188}
189
190impl BackoffBuilder for &RemoteAgentClientConfiguration {
191 type Backoff = <ConstantBuilder as BackoffBuilder>::Backoff;
192
193 fn build(self) -> Self::Backoff {
194 ConstantBuilder::default()
195 .with_delay(self.connect_retry_backoff)
196 .with_max_times(self.connect_retry_attempts)
197 .build()
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use std::path::{Path, PathBuf};
204
205 use saluki_config::ConfigurationLoader;
206
207 use super::RemoteAgentClientConfiguration;
208 use crate::platform::PlatformSettings;
209
210 async fn get_remote_agent_config(
211 ipc_cert_file_path: Option<&Path>, auth_token_file_path: Option<&Path>,
212 ) -> RemoteAgentClientConfiguration {
213 let mut values = serde_json::Map::new();
215 if let Some(path) = ipc_cert_file_path {
216 values.insert(
217 "ipc_cert_file_path".to_string(),
218 path.to_string_lossy().into_owned().into(),
219 );
220 }
221 if let Some(path) = auth_token_file_path {
222 values.insert(
223 "auth_token_file_path".to_string(),
224 path.to_string_lossy().into_owned().into(),
225 );
226 }
227
228 let (base_config, _) =
229 ConfigurationLoader::for_tests(Some(serde_json::Value::Object(values)), None, false).await;
230 RemoteAgentClientConfiguration::from_configuration(&base_config).unwrap()
231 }
232
233 #[tokio::test]
234 async fn ipc_cert_file_path_empty_config() {
235 let default_auth_token_path = PlatformSettings::get_auth_token_path();
236
237 let config = get_remote_agent_config(None, None).await;
240 assert_eq!(
241 config.auth().ipc_cert_file_path().parent(),
242 default_auth_token_path.as_path().parent()
243 );
244 assert_eq!(
245 config.auth().ipc_cert_file_path().file_name().map(Path::new),
246 Some(PlatformSettings::get_ipc_cert_filename())
247 );
248 }
249
250 #[tokio::test]
251 async fn ipc_cert_file_path_defaults() {
252 let default_auth_token_path = PlatformSettings::get_auth_token_path();
253
254 let config = get_remote_agent_config(None, Some(&default_auth_token_path)).await;
257 assert_eq!(
258 config.auth().ipc_cert_file_path().parent(),
259 default_auth_token_path.as_path().parent()
260 );
261 assert_eq!(
262 config.auth().ipc_cert_file_path().file_name().map(Path::new),
263 Some(PlatformSettings::get_ipc_cert_filename())
264 );
265 }
266
267 #[tokio::test]
268 async fn ipc_cert_file_path_explicitly_set() {
269 let default_auth_token_path = PlatformSettings::get_auth_token_path();
270 let custom_ipc_cert_path = PathBuf::from("/tmp/custom_ipc_cert.pem");
271
272 let config = get_remote_agent_config(Some(&custom_ipc_cert_path), Some(&default_auth_token_path)).await;
274 assert_eq!(custom_ipc_cert_path, config.auth().ipc_cert_file_path());
275 }
276
277 #[tokio::test]
278 async fn ipc_cert_file_path_custom_auth_token_path() {
279 let custom_auth_token_path = PathBuf::from("/secret/auth_token");
280
281 let config = get_remote_agent_config(None, Some(&custom_auth_token_path)).await;
284 assert_eq!(
285 config.auth().ipc_cert_file_path().parent(),
286 custom_auth_token_path.as_path().parent()
287 );
288 assert_eq!(
289 config.auth().ipc_cert_file_path().file_name().map(Path::new),
290 Some(PlatformSettings::get_ipc_cert_filename())
291 );
292 }
293
294 #[tokio::test]
295 async fn ipc_cert_file_path_invalid_auth_token_path() {
296 let invalid_auth_token_path = PathBuf::from("/");
297
298 let config = get_remote_agent_config(None, Some(&invalid_auth_token_path)).await;
301 assert_eq!(
302 config.auth().ipc_cert_file_path().parent(),
303 Some(PlatformSettings::get_config_dir_path())
304 );
305 assert_eq!(
306 config.auth().ipc_cert_file_path().file_name().map(Path::new),
307 Some(PlatformSettings::get_ipc_cert_filename())
308 );
309 }
310
311 async fn config_from_values(values: serde_json::Map<String, serde_json::Value>) -> RemoteAgentClientConfiguration {
312 let (base_config, _) =
313 ConfigurationLoader::for_tests(Some(serde_json::Value::Object(values)), None, false).await;
314 RemoteAgentClientConfiguration::from_configuration(&base_config).unwrap()
315 }
316
317 #[tokio::test]
318 async fn endpoint_defaults_to_port_5001() {
319 let config = config_from_values(serde_json::Map::new()).await;
320 assert_eq!(config.endpoint().unwrap().to_string(), "https://127.0.0.1:5001/");
321 }
322
323 #[tokio::test]
324 async fn endpoint_uses_cmd_port() {
325 let mut values = serde_json::Map::new();
326 values.insert("cmd_port".to_string(), 7777.into());
327 let config = config_from_values(values).await;
328 assert_eq!(config.endpoint().unwrap().to_string(), "https://127.0.0.1:7777/");
329 }
330
331 #[tokio::test]
332 async fn cmd_port_takes_precedence_over_ipc_endpoint() {
333 let mut values = serde_json::Map::new();
334 values.insert("cmd_port".to_string(), 8888.into());
335 values.insert("agent_ipc_endpoint".to_string(), "https://10.0.0.1:3333".into());
336 let config = config_from_values(values).await;
337 assert_eq!(config.endpoint().unwrap().to_string(), "https://127.0.0.1:8888/");
338 }
339
340 #[tokio::test]
341 async fn ipc_endpoint_used_when_no_cmd_port() {
342 let mut values = serde_json::Map::new();
343 values.insert("agent_ipc_endpoint".to_string(), "https://10.0.0.1:3333".into());
344 let config = config_from_values(values).await;
345 assert_eq!(config.endpoint().unwrap().to_string(), "https://10.0.0.1:3333/");
346 }
347}