docker_wrapper/command/
events.rs1use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::Result;
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone)]
35pub struct EventsCommand {
36 filters: Vec<(String, String)>,
38 format: Option<String>,
40 since: Option<String>,
42 until: Option<String>,
44 pub executor: CommandExecutor,
46}
47
48impl EventsCommand {
49 #[must_use]
59 pub fn new() -> Self {
60 Self {
61 filters: Vec::new(),
62 format: None,
63 since: None,
64 until: None,
65 executor: CommandExecutor::new(),
66 }
67 }
68
69 #[must_use]
82 pub fn filter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
83 self.filters.push((key.into(), value.into()));
84 self
85 }
86
87 #[must_use]
98 pub fn format(mut self, format: impl Into<String>) -> Self {
99 self.format = Some(format.into());
100 self
101 }
102
103 #[must_use]
114 pub fn since(mut self, since: impl Into<String>) -> Self {
115 self.since = Some(since.into());
116 self
117 }
118
119 #[must_use]
121 pub fn until(mut self, until: impl Into<String>) -> Self {
122 self.until = Some(until.into());
123 self
124 }
125
126 pub async fn run(&self) -> Result<EventsResult> {
154 let output = self.execute().await?;
155
156 let parsed_events = if self.format.as_deref() == Some("json") {
158 Self::parse_json_events(&output.stdout)
159 } else {
160 Vec::new()
161 };
162
163 Ok(EventsResult {
164 output,
165 parsed_events,
166 })
167 }
168
169 fn parse_json_events(stdout: &str) -> Vec<DockerEvent> {
171 let mut events = Vec::new();
172
173 for line in stdout.lines() {
174 let line = line.trim();
175 if line.is_empty() {
176 continue;
177 }
178
179 if let Ok(event) = serde_json::from_str::<DockerEvent>(line) {
180 events.push(event);
181 }
182 }
183
184 events
185 }
186}
187
188impl Default for EventsCommand {
189 fn default() -> Self {
190 Self::new()
191 }
192}
193
194#[async_trait]
195impl DockerCommand for EventsCommand {
196 type Output = CommandOutput;
197
198 fn build_command_args(&self) -> Vec<String> {
199 let mut args = vec!["events".to_string()];
200
201 for (key, value) in &self.filters {
202 args.push("--filter".to_string());
203 args.push(format!("{key}={value}"));
204 }
205
206 if let Some(ref format) = self.format {
207 args.push("--format".to_string());
208 args.push(format.clone());
209 }
210
211 if let Some(ref since) = self.since {
212 args.push("--since".to_string());
213 args.push(since.clone());
214 }
215
216 if let Some(ref until) = self.until {
217 args.push("--until".to_string());
218 args.push(until.clone());
219 }
220
221 args.extend(self.executor.raw_args.clone());
222 args
223 }
224
225 async fn execute(&self) -> Result<Self::Output> {
226 let args = self.build_command_args();
227 let command_name = args[0].clone();
228 let command_args = args[1..].to_vec();
229 self.executor
230 .execute_command(&command_name, command_args)
231 .await
232 }
233
234 fn get_executor(&self) -> &CommandExecutor {
235 &self.executor
236 }
237
238 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
239 &mut self.executor
240 }
241}
242
243#[derive(Debug, Clone)]
245pub struct EventsResult {
246 pub output: CommandOutput,
248 pub parsed_events: Vec<DockerEvent>,
250}
251
252impl EventsResult {
253 #[must_use]
255 pub fn success(&self) -> bool {
256 self.output.success
257 }
258
259 #[must_use]
261 pub fn parsed_events(&self) -> &[DockerEvent] {
262 &self.parsed_events
263 }
264
265 #[must_use]
267 pub fn raw_output(&self) -> &str {
268 &self.output.stdout
269 }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct DockerEvent {
275 #[serde(alias = "Type")]
277 pub event_type: String,
278
279 #[serde(alias = "Action")]
281 pub action: String,
282
283 #[serde(alias = "Actor")]
285 pub actor: EventActor,
286
287 #[serde(alias = "time")]
289 pub time: i64,
290
291 #[serde(alias = "timeNano")]
293 pub time_nano: i64,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct EventActor {
299 #[serde(alias = "ID")]
301 pub id: String,
302
303 #[serde(alias = "Attributes")]
305 pub attributes: std::collections::HashMap<String, String>,
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_events_basic() {
314 let cmd = EventsCommand::new();
315 let args = cmd.build_command_args();
316 assert_eq!(args, vec!["events"]);
317 }
318
319 #[test]
320 fn test_events_with_filters() {
321 let cmd = EventsCommand::new()
322 .filter("type", "container")
323 .filter("event", "start");
324 let args = cmd.build_command_args();
325 assert_eq!(
326 args,
327 vec![
328 "events",
329 "--filter",
330 "type=container",
331 "--filter",
332 "event=start"
333 ]
334 );
335 }
336
337 #[test]
338 fn test_events_with_format() {
339 let cmd = EventsCommand::new().format("json");
340 let args = cmd.build_command_args();
341 assert_eq!(args, vec!["events", "--format", "json"]);
342 }
343
344 #[test]
345 fn test_events_with_since_until() {
346 let cmd = EventsCommand::new()
347 .since("2023-01-01T00:00:00")
348 .until("2023-12-31T23:59:59");
349 let args = cmd.build_command_args();
350 assert_eq!(
351 args,
352 vec![
353 "events",
354 "--since",
355 "2023-01-01T00:00:00",
356 "--until",
357 "2023-12-31T23:59:59"
358 ]
359 );
360 }
361
362 #[test]
363 fn test_events_all_options() {
364 let cmd = EventsCommand::new()
365 .filter("type", "container")
366 .filter("container", "my-app")
367 .format("json")
368 .since("1h")
369 .until("now");
370 let args = cmd.build_command_args();
371 assert_eq!(
372 args,
373 vec![
374 "events",
375 "--filter",
376 "type=container",
377 "--filter",
378 "container=my-app",
379 "--format",
380 "json",
381 "--since",
382 "1h",
383 "--until",
384 "now"
385 ]
386 );
387 }
388
389 #[test]
390 fn test_parse_json_events() {
391 let json_output = r#"{"Type":"container","Action":"start","Actor":{"ID":"abc123","Attributes":{"name":"test-container"}},"time":1640995200,"timeNano":1640995200000000000}"#;
392
393 let events = EventsCommand::parse_json_events(json_output);
394 assert_eq!(events.len(), 1);
395 assert_eq!(events[0].event_type, "container");
396 assert_eq!(events[0].action, "start");
397 assert_eq!(events[0].actor.id, "abc123");
398 }
399
400 #[test]
401 fn test_parse_json_events_empty() {
402 let events = EventsCommand::parse_json_events("");
403 assert!(events.is_empty());
404 }
405}