docker_wrapper/command/
events.rs

1//! Docker events command implementation.
2//!
3//! This module provides the `docker events` command for getting real-time events from the Docker daemon.
4
5use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::Result;
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9
10/// Docker events command builder
11///
12/// Get real-time events from the Docker daemon.
13///
14/// # Example
15///
16/// ```no_run
17/// use docker_wrapper::EventsCommand;
18///
19/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
20/// // Get all events
21/// let events = EventsCommand::new()
22///     .run()
23///     .await?;
24///
25/// // Get container events only
26/// let container_events = EventsCommand::new()
27///     .filter("type", "container")
28///     .format("json")
29///     .run()
30///     .await?;
31/// # Ok(())
32/// # }
33/// ```
34#[derive(Debug, Clone)]
35pub struct EventsCommand {
36    /// Event filters
37    filters: Vec<(String, String)>,
38    /// Output format
39    format: Option<String>,
40    /// Show events since timestamp
41    since: Option<String>,
42    /// Show events until timestamp  
43    until: Option<String>,
44    /// Command executor
45    pub executor: CommandExecutor,
46}
47
48impl EventsCommand {
49    /// Create a new events command
50    ///
51    /// # Example
52    ///
53    /// ```
54    /// use docker_wrapper::EventsCommand;
55    ///
56    /// let cmd = EventsCommand::new();
57    /// ```
58    #[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    /// Add a filter for events
70    ///
71    /// # Example
72    ///
73    /// ```
74    /// use docker_wrapper::EventsCommand;
75    ///
76    /// let cmd = EventsCommand::new()
77    ///     .filter("type", "container")
78    ///     .filter("event", "start")
79    ///     .filter("container", "my-container");
80    /// ```
81    #[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    /// Set output format
88    ///
89    /// # Example
90    ///
91    /// ```
92    /// use docker_wrapper::EventsCommand;
93    ///
94    /// // JSON format for programmatic parsing
95    /// let cmd = EventsCommand::new().format("json");
96    /// ```
97    #[must_use]
98    pub fn format(mut self, format: impl Into<String>) -> Self {
99        self.format = Some(format.into());
100        self
101    }
102
103    /// Show events created since this timestamp
104    ///
105    /// # Example
106    ///
107    /// ```
108    /// use docker_wrapper::EventsCommand;
109    ///
110    /// let cmd = EventsCommand::new()
111    ///     .since("2023-01-01T00:00:00");
112    /// ```
113    #[must_use]
114    pub fn since(mut self, since: impl Into<String>) -> Self {
115        self.since = Some(since.into());
116        self
117    }
118
119    /// Show events created until this timestamp
120    #[must_use]
121    pub fn until(mut self, until: impl Into<String>) -> Self {
122        self.until = Some(until.into());
123        self
124    }
125
126    /// Execute the events command
127    ///
128    /// # Errors
129    /// Returns an error if:
130    /// - The Docker daemon is not running
131    /// - Invalid filter or timestamp format
132    ///
133    /// # Example
134    ///
135    /// ```no_run
136    /// use docker_wrapper::EventsCommand;
137    ///
138    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
139    /// let result = EventsCommand::new()
140    ///     .filter("type", "container")
141    ///     .format("json")
142    ///     .run()
143    ///     .await?;
144    ///
145    /// if result.success() {
146    ///     for event in result.parsed_events() {
147    ///         println!("Event: {} on {}", event.action, event.actor.id);
148    ///     }
149    /// }
150    /// # Ok(())
151    /// # }
152    /// ```
153    pub async fn run(&self) -> Result<EventsResult> {
154        let output = self.execute().await?;
155
156        // Parse events if JSON format was used
157        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    /// Parse JSON events output into structured data
170    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/// Result from the events command
244#[derive(Debug, Clone)]
245pub struct EventsResult {
246    /// Raw command output
247    pub output: CommandOutput,
248    /// Parsed events (when JSON format is used)
249    pub parsed_events: Vec<DockerEvent>,
250}
251
252impl EventsResult {
253    /// Check if the events command was successful
254    #[must_use]
255    pub fn success(&self) -> bool {
256        self.output.success
257    }
258
259    /// Get parsed events (available when JSON format is used)
260    #[must_use]
261    pub fn parsed_events(&self) -> &[DockerEvent] {
262        &self.parsed_events
263    }
264
265    /// Get the raw events output
266    #[must_use]
267    pub fn raw_output(&self) -> &str {
268        &self.output.stdout
269    }
270}
271
272/// Docker event information
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct DockerEvent {
275    /// Event type (container, image, volume, network, etc.)
276    #[serde(alias = "Type")]
277    pub event_type: String,
278
279    /// Action performed (start, stop, create, destroy, etc.)
280    #[serde(alias = "Action")]
281    pub action: String,
282
283    /// Actor (object that performed the action)
284    #[serde(alias = "Actor")]
285    pub actor: EventActor,
286
287    /// Timestamp of the event
288    #[serde(alias = "time")]
289    pub time: i64,
290
291    /// Nanosecond timestamp
292    #[serde(alias = "timeNano")]
293    pub time_nano: i64,
294}
295
296/// Actor information for Docker events
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct EventActor {
299    /// Actor ID (container ID, image ID, etc.)
300    #[serde(alias = "ID")]
301    pub id: String,
302
303    /// Actor attributes (name, image, etc.)
304    #[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}