lightshuttle_runtime/lifecycle/
handle.rs1use std::sync::Arc;
11
12use thiserror::Error;
13use tokio::sync::broadcast;
14
15use crate::error::RuntimeError;
16use crate::lifecycle::manager::LifecycleManager;
17use crate::lifecycle::status::LifecycleEvent;
18use crate::lifecycle::view::{ResourceStatus, ResourceView, image_label, last_error_from};
19use crate::runtime::{ContainerRuntime, LogChunkStream};
20
21#[derive(Debug, Error)]
23pub enum LifecycleHandleError {
24 #[error("resource `{0}` does not exist in the current plan")]
26 UnknownResource(String),
27 #[error("operation `{0}` is not supported by this handle yet")]
30 NotSupported(&'static str),
31 #[error(transparent)]
33 Runtime(#[from] RuntimeError),
34}
35
36pub trait LifecycleHandle: Send + Sync {
41 fn list(
43 &self,
44 ) -> impl std::future::Future<Output = Result<Vec<ResourceView>, LifecycleHandleError>> + Send;
45
46 fn get(
48 &self,
49 name: &str,
50 ) -> impl std::future::Future<Output = Result<ResourceView, LifecycleHandleError>> + Send;
51
52 fn restart(
54 &self,
55 name: &str,
56 ) -> impl std::future::Future<Output = Result<(), LifecycleHandleError>> + Send;
57
58 fn logs(
61 &self,
62 name: &str,
63 follow: bool,
64 ) -> impl std::future::Future<Output = Result<LogChunkStream, LifecycleHandleError>> + Send;
65
66 fn subscribe_events(&self) -> broadcast::Receiver<LifecycleEvent>;
71}
72
73pub struct ManagerHandle<R: ContainerRuntime + 'static> {
76 inner: Arc<LifecycleManager<R>>,
77}
78
79impl<R: ContainerRuntime + 'static> Clone for ManagerHandle<R> {
83 fn clone(&self) -> Self {
84 Self {
85 inner: Arc::clone(&self.inner),
86 }
87 }
88}
89
90impl<R: ContainerRuntime + 'static> ManagerHandle<R> {
91 #[must_use]
93 pub fn new(inner: Arc<LifecycleManager<R>>) -> Self {
94 Self { inner }
95 }
96
97 #[must_use]
99 pub fn manager(&self) -> &Arc<LifecycleManager<R>> {
100 &self.inner
101 }
102}
103
104impl<R: ContainerRuntime + 'static> LifecycleHandle for ManagerHandle<R> {
105 async fn list(&self) -> Result<Vec<ResourceView>, LifecycleHandleError> {
106 let plan = self.inner.plan_arc();
107 let mut out: Vec<ResourceView> = Vec::with_capacity(plan.nodes().len());
108 for node in plan.nodes() {
109 let snapshot = self
110 .inner
111 .snapshot(&node.name)
112 .ok_or_else(|| LifecycleHandleError::UnknownResource(node.name.clone()))?;
113 out.push(ResourceView {
114 name: node.name.clone(),
115 kind: node.kind.clone(),
116 status: ResourceStatus::from(&snapshot.status),
117 healthy: matches!(
118 snapshot.status,
119 crate::lifecycle::status::NodeStatus::Healthy
120 ),
121 image: image_label(&node.spec.image),
122 started_at: snapshot.started_at,
123 last_error: last_error_from(&snapshot.status),
124 });
125 }
126 Ok(out)
127 }
128
129 async fn get(&self, name: &str) -> Result<ResourceView, LifecycleHandleError> {
130 let plan = self.inner.plan_arc();
131 let node = plan
132 .nodes()
133 .iter()
134 .find(|n| n.name == name)
135 .ok_or_else(|| LifecycleHandleError::UnknownResource(name.to_owned()))?;
136 let snapshot = self
137 .inner
138 .snapshot(name)
139 .ok_or_else(|| LifecycleHandleError::UnknownResource(name.to_owned()))?;
140 Ok(ResourceView {
141 name: node.name.clone(),
142 kind: node.kind.clone(),
143 status: ResourceStatus::from(&snapshot.status),
144 healthy: matches!(
145 snapshot.status,
146 crate::lifecycle::status::NodeStatus::Healthy
147 ),
148 image: image_label(&node.spec.image),
149 started_at: snapshot.started_at,
150 last_error: last_error_from(&snapshot.status),
151 })
152 }
153
154 async fn restart(&self, name: &str) -> Result<(), LifecycleHandleError> {
155 self.inner.restart_one(name).await.map_err(|err| match err {
156 crate::LifecycleError::ResourceNotFound(name) => {
157 LifecycleHandleError::UnknownResource(name)
158 }
159 crate::LifecycleError::Start { source, .. }
160 | crate::LifecycleError::Stop { source, .. }
161 | crate::LifecycleError::SpecBuild { source, .. } => {
162 LifecycleHandleError::Runtime(source)
163 }
164 other => LifecycleHandleError::Runtime(RuntimeError::InvalidSpec(other.to_string())),
165 })
166 }
167
168 async fn logs(&self, name: &str, follow: bool) -> Result<LogChunkStream, LifecycleHandleError> {
169 let plan = self.inner.plan_arc();
170 if !plan.nodes().iter().any(|n| n.name == name) {
171 return Err(LifecycleHandleError::UnknownResource(name.to_owned()));
172 }
173 let snapshot = self
174 .inner
175 .snapshot(name)
176 .ok_or_else(|| LifecycleHandleError::UnknownResource(name.to_owned()))?;
177 let container_id = snapshot.container_id.ok_or_else(|| {
178 LifecycleHandleError::Runtime(RuntimeError::InvalidSpec(format!(
179 "resource `{name}` is not running"
180 )))
181 })?;
182 let stream = self.inner.runtime_arc().logs(&container_id, follow).await?;
183 Ok(stream)
184 }
185
186 fn subscribe_events(&self) -> broadcast::Receiver<LifecycleEvent> {
187 self.inner.subscribe_events()
188 }
189}