datadog_agent_commons/ipc/client/
mod.rs1use std::time::Duration;
4
5use backon::Retryable as _;
6use datadog_protos::agent::v1::{
7 RefreshRemoteAgentRequest, RegisterRemoteAgentRequest, RegisterRemoteAgentResponse, ReportRemoteAgentEventRequest,
8 ReportRemoteAgentEventResponse,
9};
10use datadog_protos::agent::{
11 AgentClient, AgentSecureClient, AutodiscoveryStreamResponse, ConfigEvent, ConfigStreamRequest, EntityId,
12 FetchEntityRequest, HostTagReply, HostTagRequest, HostnameRequest, RemoteAgentClient as RemoteAgentServiceClient,
13 StreamTagsRequest, StreamTagsResponse, TagCardinality, WorkloadmetaEventType, WorkloadmetaFilter, WorkloadmetaKind,
14 WorkloadmetaSource, WorkloadmetaStreamRequest, WorkloadmetaStreamResponse,
15};
16use saluki_config::GenericConfiguration;
17use saluki_error::{generic_error, ErrorContext as _, GenericError};
18use saluki_io::net::client::http::HttpsCapableConnectorBuilder;
19use tonic::{
20 service::interceptor::InterceptedService,
21 transport::{Channel, Endpoint},
22 Code, Request, Response,
23};
24use tracing::warn;
25
26use crate::ipc::{config::RemoteAgentClientConfiguration, session::SessionId, tls::build_ipc_client_ipc_tls_config};
27
28mod bearer_auth;
29use self::bearer_auth::BearerAuthInterceptor;
30
31mod streaming;
32pub use self::streaming::StreamingResponse;
33
34#[derive(Clone)]
36pub struct RemoteAgentClient {
37 client: AgentClient<InterceptedService<Channel, BearerAuthInterceptor>>,
38 secure_client: AgentSecureClient<InterceptedService<Channel, BearerAuthInterceptor>>,
39 remote_agent_client: RemoteAgentServiceClient<InterceptedService<Channel, BearerAuthInterceptor>>,
40}
41
42impl RemoteAgentClient {
43 pub async fn from_configuration(config: &GenericConfiguration) -> Result<Self, GenericError> {
50 let config = RemoteAgentClientConfiguration::from_configuration(config)?;
51 Self::from_client_configuration(&config).await
52 }
53
54 pub async fn from_client_configuration(config: &RemoteAgentClientConfiguration) -> Result<Self, GenericError> {
61 let service_builder = || async {
72 let auth_interceptor = BearerAuthInterceptor::from_file(&config.auth().auth_token_file_path()).await?;
73 let ipc_cert_file_path = config.auth().ipc_cert_file_path();
74 let client_tls_config = build_ipc_client_ipc_tls_config(ipc_cert_file_path).await?;
75 let connector_builder = HttpsCapableConnectorBuilder::default();
76 #[cfg(target_os = "linux")]
77 let connector_builder = if let Some(addr) = config.vsock_addr()? {
78 connector_builder.with_vsock_addr(addr)
79 } else {
80 connector_builder
81 };
82 let https_connector = connector_builder.build(client_tls_config)?;
83 let endpoint = config.endpoint()?;
84 let channel = Endpoint::from(endpoint.clone())
85 .connect_timeout(Duration::from_secs(2))
86 .connect_with_connector(https_connector)
87 .await
88 .with_error_context(|| format!("Failed to connect to Datadog Agent API at '{}'.", endpoint))?;
89
90 let service = InterceptedService::new(channel, auth_interceptor);
91
92 let mut secure_client =
95 AgentSecureClient::new(service.clone()).max_decoding_message_size(config.grpc_max_message_size());
96 try_query_agent_api(&mut secure_client).await?;
97
98 Ok::<_, GenericError>(service)
99 };
100
101 let service = service_builder
102 .retry(config)
103 .notify(|e, delay| {
104 warn!(error = %e, "Failed to create Datadog Agent API client. Retrying in {:?}...", delay);
105 })
106 .await
107 .error_context("Failed to create Datadog Agent API client.")?;
108
109 let client = AgentClient::new(service.clone()).max_decoding_message_size(config.grpc_max_message_size());
110 let secure_client =
111 AgentSecureClient::new(service.clone()).max_decoding_message_size(config.grpc_max_message_size());
112 let remote_agent_client =
113 RemoteAgentServiceClient::new(service).max_decoding_message_size(config.grpc_max_message_size());
114
115 Ok(Self {
116 client,
117 secure_client,
118 remote_agent_client,
119 })
120 }
121
122 pub async fn get_hostname(&mut self) -> Result<String, GenericError> {
128 let response = self
129 .client
130 .get_hostname(HostnameRequest {})
131 .await
132 .map(|r| r.into_inner())?;
133
134 Ok(response.hostname)
135 }
136
137 pub fn get_tagger_stream(
146 &mut self, cardinality: TagCardinality, prefixes: Option<Vec<String>>,
147 ) -> StreamingResponse<StreamTagsResponse> {
148 let mut client = self.secure_client.clone();
149 StreamingResponse::from_response_future(async move {
150 client
151 .tagger_stream_entities(StreamTagsRequest {
152 cardinality: cardinality.into(),
153 prefixes: prefixes.unwrap_or_default(),
154 ..Default::default()
155 })
156 .await
157 })
158 }
159
160 pub fn get_workloadmeta_stream(&mut self) -> StreamingResponse<WorkloadmetaStreamResponse> {
165 let mut client = self.secure_client.clone();
166 StreamingResponse::from_response_future(async move {
167 client
168 .workloadmeta_stream_entities(WorkloadmetaStreamRequest {
169 filter: Some(WorkloadmetaFilter {
170 kinds: vec![
171 WorkloadmetaKind::Container.into(),
172 WorkloadmetaKind::KubernetesPod.into(),
173 WorkloadmetaKind::EcsTask.into(),
174 ],
175 source: WorkloadmetaSource::All.into(),
176 event_type: WorkloadmetaEventType::EventTypeAll.into(),
177 }),
178 })
179 .await
180 })
181 }
182
183 pub async fn register_remote_agent(
189 &mut self, pid: u32, display_name: &str, flavor: &str, api_endpoint: &str, services: Vec<String>,
190 ) -> Result<Response<RegisterRemoteAgentResponse>, GenericError> {
191 let mut client = self.remote_agent_client.clone();
192 let response = client
193 .register_remote_agent(RegisterRemoteAgentRequest {
194 pid: pid.to_string(),
195 flavor: flavor.to_string(),
196 display_name: display_name.to_string(),
197 api_endpoint_uri: api_endpoint.to_string(),
198 services,
199 })
200 .await?;
201 Ok(response)
202 }
203
204 pub async fn refresh_remote_agent(&mut self, session_id: &SessionId) -> Result<Response<()>, GenericError> {
210 let mut client = self.remote_agent_client.clone();
211 let response = client
212 .refresh_remote_agent(RefreshRemoteAgentRequest {
213 session_id: session_id.to_string(),
214 })
215 .await?
216 .map(|_| ());
217 Ok(response)
218 }
219
220 pub async fn report_remote_agent_event(
226 &mut self, request: ReportRemoteAgentEventRequest,
227 ) -> Result<Response<ReportRemoteAgentEventResponse>, GenericError> {
228 let mut client = self.remote_agent_client.clone();
229 let response = client.report_remote_agent_event(request).await?;
230 Ok(response)
231 }
232
233 pub async fn get_host_tags(&self) -> Result<Response<HostTagReply>, GenericError> {
239 let mut client = self.secure_client.clone();
240 let response = client.get_host_tags(HostTagRequest {}).await?;
241 Ok(response)
242 }
243
244 pub fn get_autodiscovery_stream(&mut self) -> StreamingResponse<AutodiscoveryStreamResponse> {
249 let mut client = self.secure_client.clone();
250 StreamingResponse::from_response_future(async move { client.autodiscovery_stream_config(()).await })
251 }
252
253 pub fn stream_config_events(&mut self, session_id: &SessionId) -> StreamingResponse<ConfigEvent> {
258 let mut client = self.secure_client.clone();
259 let app_details = saluki_metadata::get_app_details();
260 let formatted_full_name = app_details
261 .full_name()
262 .replace(" ", "-")
263 .replace("_", "-")
264 .to_lowercase();
265
266 let mut request = Request::new(ConfigStreamRequest {
267 name: formatted_full_name,
268 });
269
270 request
271 .metadata_mut()
272 .insert("session_id", session_id.to_grpc_header_value());
273
274 StreamingResponse::from_response_future(async move { client.stream_config_events(request).await })
275 }
276}
277
278async fn try_query_agent_api(
279 client: &mut AgentSecureClient<InterceptedService<Channel, BearerAuthInterceptor>>,
280) -> Result<(), GenericError> {
281 let noop_fetch_request = FetchEntityRequest {
282 id: Some(EntityId {
283 prefix: "container_id".to_string(),
284 uid: "nonexistent".to_string(),
285 }),
286 cardinality: TagCardinality::High.into(),
287 };
288 match client.tagger_fetch_entity(noop_fetch_request).await {
289 Ok(_) => Ok(()),
290 Err(e) => match e.code() {
291 Code::Unauthenticated => Err(generic_error!(
292 "Failed to authenticate to Datadog Agent API. Check that the configured authentication token is correct."
293 )),
294 _ => Err(e.into()),
295 },
296 }
297}