olai_stack_runtime/
events.rs1use serde::{Deserialize, Serialize};
4
5use crate::status::Health;
6
7pub const PROJECT_LABEL: &str = "com.docker.compose.project";
9pub const SERVICE_LABEL: &str = "com.docker.compose.service";
11
12#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
14#[serde(rename_all = "snake_case")]
15pub enum EventAction {
16 Create,
18 Start,
20 Stop,
22 Kill,
24 Die {
26 exit_code: Option<i32>,
28 },
29 Destroy,
31 Health(Health),
33 Other(String),
35}
36
37#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
39pub struct EngineEvent {
40 pub project: String,
42 pub service: String,
44 pub container: String,
46 pub action: EventAction,
48 pub time_nanos: i64,
50}
51
52pub fn parse_event(line: &str) -> Result<Option<EngineEvent>, serde_json::Error> {
55 #[derive(Deserialize)]
56 struct Actor {
57 #[serde(rename = "Attributes", default)]
58 attributes: std::collections::BTreeMap<String, String>,
59 }
60 #[derive(Deserialize)]
61 struct Raw {
62 #[serde(rename = "Type", default)]
63 kind: String,
64 #[serde(rename = "Action", default)]
65 action: String,
66 #[serde(rename = "Actor")]
67 actor: Actor,
68 #[serde(rename = "timeNano", default)]
69 time_nano: i64,
70 }
71 let raw: Raw = serde_json::from_str(line)?;
72 if raw.kind != "container" {
73 return Ok(None);
74 }
75 let attrs = &raw.actor.attributes;
76 let (Some(project), Some(service)) = (attrs.get(PROJECT_LABEL), attrs.get(SERVICE_LABEL))
77 else {
78 return Ok(None);
79 };
80 let action = match raw.action.split_once(':') {
81 Some(("health_status", health)) => EventAction::Health(Health::parse(health)),
82 Some((name, _)) if name.starts_with("exec_") => return Ok(None),
83 _ => match raw.action.as_str() {
84 "create" => EventAction::Create,
85 "start" => EventAction::Start,
86 "stop" => EventAction::Stop,
87 "kill" => EventAction::Kill,
88 "die" => EventAction::Die {
89 exit_code: attrs.get("exitCode").and_then(|c| c.parse().ok()),
90 },
91 "destroy" => EventAction::Destroy,
92 other if other.starts_with("exec_") => return Ok(None),
93 other => EventAction::Other(other.to_owned()),
94 },
95 };
96 Ok(Some(EngineEvent {
97 project: project.clone(),
98 service: service.clone(),
99 container: attrs.get("name").cloned().unwrap_or_default(),
100 action,
101 time_nanos: raw.time_nano,
102 }))
103}
104
105#[derive(Clone, Debug, PartialEq, Eq)]
107pub enum EventFilter {
108 Project(String),
110 Prefix(String),
112 All,
114}
115
116impl EventFilter {
117 pub fn matches(&self, event: &EngineEvent) -> bool {
119 match self {
120 EventFilter::Project(name) => event.project == *name,
121 EventFilter::Prefix(prefix) => event.project.starts_with(prefix.as_str()),
122 EventFilter::All => true,
123 }
124 }
125}