kibana_sync/kibana/saved_objects/
extractor.rs1use crate::client::KibanaClient;
6use crate::etl::Extractor;
7
8use crate::{Error, Result, ResultContext};
9use serde_json::Value;
10
11pub struct SavedObjectsExtractor {
36 client: KibanaClient,
37 manifest: super::SavedObjectsManifest,
38}
39
40impl SavedObjectsExtractor {
41 pub fn new(client: KibanaClient, manifest: super::SavedObjectsManifest) -> Self {
47 Self { client, manifest }
48 }
49
50 async fn export_objects(&self) -> Result<Vec<Value>> {
55 let path = "api/saved_objects/_export";
56
57 tracing::debug!(
58 "Exporting {} object(s) from space '{}'",
59 self.manifest.count(),
60 self.client.space_id()
61 );
62
63 let response = self
64 .client
65 .post_json_value(path, &serde_json::to_value(&self.manifest)?)
66 .await
67 .with_context(|| "Failed to export saved objects from Kibana")?;
68
69 if !response.status().is_success() {
70 let status = response.status();
71 let body = response.text().await.unwrap_or_default();
72 return Err(Error::api_response(status, body));
73 }
74
75 let body = response
77 .text()
78 .await
79 .with_context(|| "Failed to read export response")?;
80
81 let objects: Vec<Value> = body
82 .lines()
83 .filter(|line| !line.trim().is_empty())
84 .map(serde_json::from_str)
85 .collect::<std::result::Result<Vec<_>, serde_json::Error>>()
86 .with_context(|| "Failed to parse NDJSON response")?;
87
88 tracing::info!("Exported {} object(s) from Kibana", objects.len());
89
90 Ok(objects)
91 }
92}
93
94impl Extractor for SavedObjectsExtractor {
95 type Item = Value;
96
97 async fn extract(&self) -> Result<Vec<Self::Item>> {
98 let objects = self.export_objects().await?;
99
100 tracing::info!("Extracted {} saved object(s)", objects.len());
101
102 Ok(objects)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::client::{Auth, KibanaClient};
110 use crate::kibana::saved_objects::SavedObjectsManifest;
111 use crate::kibana::saved_objects::manifest::SavedObject;
112 use url::Url;
113
114 #[test]
115 fn test_extractor_creation() {
116 let url = Url::parse("http://localhost:5601").unwrap();
117 let client = KibanaClient::new(url, Auth::None).unwrap();
118 let space_client = client.space("default").unwrap();
119 let manifest = SavedObjectsManifest::new();
120 let extractor = SavedObjectsExtractor::new(space_client, manifest);
121 assert_eq!(extractor.client.space_id(), "default");
122 }
123
124 #[test]
125 fn test_extractor_with_manifest() {
126 let url = Url::parse("http://localhost:5601").unwrap();
127 let client = KibanaClient::builder(url)
128 .spaces([
129 ("default".to_string(), "Default".to_string()),
130 ("marketing".to_string(), "Marketing".to_string()),
131 ])
132 .build()
133 .unwrap();
134 let space_client = client.space("marketing").unwrap();
135
136 let mut so_manifest = SavedObjectsManifest::new();
137 so_manifest.add_object(SavedObject::new("dashboard", "test-dashboard"));
138 so_manifest.add_object(SavedObject::new("visualization", "test-viz"));
139
140 let extractor = SavedObjectsExtractor::new(space_client, so_manifest.clone());
141 assert_eq!(extractor.client.space_id(), "marketing");
142 assert_eq!(extractor.manifest.count(), 2);
143 }
144}