datadog_agent_runtime/control_plane/
config_runtime.rs1use std::sync::Arc;
4
5use arc_swap::ArcSwap;
6use async_trait::async_trait;
7use saluki_api::{
8 extract::State,
9 response::IntoResponse,
10 routing::{get, Router},
11 APIHandler, DynamicRoute, EndpointType, StatusCode,
12};
13use saluki_common::sync::shutdown::ShutdownHandle;
14use saluki_core::runtime::{state::DataspaceRegistry, InitializationError, Supervisable, SupervisorFuture};
15use saluki_error::generic_error;
16use serde::Serialize;
17
18#[derive(Clone)]
20pub struct RuntimeConfigSnapshot {
21 serialize: Arc<dyn Fn() -> Result<String, serde_json::Error> + Send + Sync>,
22}
23
24impl RuntimeConfigSnapshot {
25 pub fn from_shared<T>(current: Arc<ArcSwap<T>>) -> Self
28 where
29 T: Serialize + Send + Sync + 'static,
30 {
31 Self {
32 serialize: Arc::new(move || serde_json::to_string(&**current.load())),
33 }
34 }
35
36 fn to_json(&self) -> Result<String, serde_json::Error> {
37 (self.serialize)()
38 }
39}
40
41#[derive(Clone)]
43pub struct ConfigRuntimeState {
44 snapshot: RuntimeConfigSnapshot,
45}
46
47pub struct ConfigRuntimeAPIHandler {
53 state: ConfigRuntimeState,
54}
55
56impl ConfigRuntimeAPIHandler {
57 fn new(snapshot: RuntimeConfigSnapshot) -> Self {
58 Self {
59 state: ConfigRuntimeState { snapshot },
60 }
61 }
62
63 async fn config_handler(State(state): State<ConfigRuntimeState>) -> impl IntoResponse {
64 match state.snapshot.to_json() {
65 Ok(body) => (StatusCode::OK, body).into_response(),
66 Err(e) => (
67 StatusCode::INTERNAL_SERVER_ERROR,
68 format!("Failed to serialize configuration: {}", e),
69 )
70 .into_response(),
71 }
72 }
73}
74
75impl APIHandler for ConfigRuntimeAPIHandler {
76 type State = ConfigRuntimeState;
77
78 fn generate_initial_state(&self) -> Self::State {
79 self.state.clone()
80 }
81
82 fn generate_routes(&self) -> Router<Self::State> {
83 Router::new().route("/config/runtime", get(Self::config_handler))
84 }
85}
86
87pub struct ConfigRuntimeWorker {
93 handler: ConfigRuntimeAPIHandler,
94}
95
96impl ConfigRuntimeWorker {
97 pub fn new(snapshot: RuntimeConfigSnapshot) -> Self {
99 Self {
100 handler: ConfigRuntimeAPIHandler::new(snapshot),
101 }
102 }
103}
104
105#[async_trait]
106impl Supervisable for ConfigRuntimeWorker {
107 fn name(&self) -> &str {
108 "config-runtime-api"
109 }
110
111 async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
112 let config_route = DynamicRoute::http(EndpointType::Privileged, &self.handler);
113
114 Ok(Box::pin(async move {
115 let dataspace =
116 DataspaceRegistry::try_current().ok_or_else(|| generic_error!("Dataspace not available."))?;
117
118 dataspace.assert(config_route, "config-runtime-api");
119
120 process_shutdown.await;
121 Ok(())
122 }))
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[derive(Serialize)]
131 struct ExampleConfig {
132 log_level: String,
133 }
134
135 #[test]
136 fn shared_snapshot_follows_updates_to_the_shared_configuration() {
137 let current = Arc::new(ArcSwap::from_pointee(ExampleConfig {
138 log_level: "info".to_string(),
139 }));
140 let snapshot = RuntimeConfigSnapshot::from_shared(Arc::clone(¤t));
141 assert_eq!(snapshot.to_json().unwrap(), r#"{"log_level":"info"}"#);
142
143 current.store(Arc::new(ExampleConfig {
144 log_level: "debug".to_string(),
145 }));
146 assert_eq!(snapshot.to_json().unwrap(), r#"{"log_level":"debug"}"#);
147 }
148}