Skip to main content

lightshuttle_runtime/lifecycle/
view.rs

1//! Lightweight value types exposed by the control plane.
2//!
3//! Built on demand by [`crate::ManagerHandle`] and surfaced through the
4//! [`crate::LifecycleHandle`] trait. The shape is intentionally stable across
5//! runtime backends so callers (REST API, dashboard, CLI) never see
6//! backend-specific types.
7//!
8//! [`ResourceView`] is the main carrier: it flattens [`crate::NodeStatus`]
9//! into the coarser [`ResourceStatus`] and adds display-friendly fields such
10//! as the resolved image reference and a last-error string.
11
12use std::time::SystemTime;
13
14use serde::{Deserialize, Serialize};
15
16use crate::lifecycle::status::NodeStatus;
17use lightshuttle_spec::ImageSource;
18
19/// Dashboard-friendly view of a single managed resource.
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct ResourceView {
22    /// Manifest-declared resource name.
23    pub name: String,
24    /// Resource kind discriminant (`postgres`, `redis`, `container`, `dockerfile`).
25    pub kind: String,
26    /// Coarse-grained status suitable for UI rendering.
27    pub status: ResourceStatus,
28    /// Whether the resource passed its healthcheck.
29    pub healthy: bool,
30    /// Container image reference, as resolved at start time.
31    pub image: String,
32    /// Wall-clock time at which the runtime accepted the start request.
33    pub started_at: Option<SystemTime>,
34    /// Last terminal failure reason, when applicable.
35    pub last_error: Option<String>,
36}
37
38/// Coarse-grained resource status, derived from [`NodeStatus`] and
39/// flattened for UI consumption.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41pub enum ResourceStatus {
42    /// Not started yet.
43    Pending,
44    /// Container is booting up.
45    Starting,
46    /// Container is up, with or without a green healthcheck.
47    Running,
48    /// Resource has entered a terminal failure state.
49    Failed,
50    /// Resource has been stopped on request.
51    Stopped,
52}
53
54impl From<&NodeStatus> for ResourceStatus {
55    fn from(status: &NodeStatus) -> Self {
56        match status {
57            NodeStatus::Pending => Self::Pending,
58            NodeStatus::Starting => Self::Starting,
59            NodeStatus::Running | NodeStatus::Healthy => Self::Running,
60            NodeStatus::Failed { .. } => Self::Failed,
61            NodeStatus::Stopped => Self::Stopped,
62        }
63    }
64}
65
66/// Render an [`ImageSource`] as the user-facing image reference: the
67/// pulled image string for `Pull`, the produced tag for `Build`.
68pub(crate) fn image_label(src: &ImageSource) -> String {
69    match src {
70        ImageSource::Pull(s) => s.clone(),
71        ImageSource::Build { tag, .. } => tag.clone(),
72    }
73}
74
75/// Extract the last error message from a [`NodeStatus`], when terminal.
76pub(crate) fn last_error_from(status: &NodeStatus) -> Option<String> {
77    match status {
78        NodeStatus::Failed { reason } => Some(reason.clone()),
79        _ => None,
80    }
81}