Skip to main content

rskit_workload/
spec.rs

1//! Deployment request and its nested specification types.
2
3use std::collections::HashMap;
4use std::time::Duration;
5
6use crate::state::{RestartPolicy, WorkloadState};
7
8/// Describes a workload to deploy.
9///
10/// Construct with [`DeployRequest::new`] and set the remaining fields directly.
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
12pub struct DeployRequest {
13    /// Human-readable identifier (container name, pod name).
14    pub name: String,
15    /// Image reference.
16    pub image: String,
17    /// Override entrypoint/command.
18    pub command: Vec<String>,
19    /// Arguments passed to the command.
20    pub args: Vec<String>,
21    /// Environment variables.
22    pub environment: HashMap<String, String>,
23    /// Key-value pairs for filtering and grouping.
24    pub labels: HashMap<String, String>,
25    /// Metadata annotations (Kubernetes).
26    pub annotations: HashMap<String, String>,
27    /// Working directory inside the workload.
28    pub work_dir: String,
29    /// CPU/memory constraints.
30    pub resources: Option<ResourceConfig>,
31    /// Network configuration.
32    pub network: Option<NetworkConfig>,
33    /// Mount points.
34    pub volumes: Vec<VolumeMount>,
35    /// Port mappings.
36    pub ports: Vec<PortMapping>,
37    /// Restart behavior after exit.
38    pub restart_policy: RestartPolicy,
39    /// Remove the workload after it exits (Docker).
40    pub auto_remove: bool,
41    /// Number of replicas (Kubernetes); `0` means the provider default.
42    pub replicas: u32,
43    /// Maximum run time; `None` means no limit.
44    pub timeout: Option<Duration>,
45    /// Target platform (e.g. `"linux/amd64"`).
46    pub platform: String,
47    /// Namespace (Kubernetes); empty means the default namespace.
48    pub namespace: String,
49    /// Service account (Kubernetes).
50    pub service_account: String,
51}
52
53impl DeployRequest {
54    /// Create a request for the given workload `name` and `image`.
55    #[must_use]
56    pub fn new(name: impl Into<String>, image: impl Into<String>) -> Self {
57        Self {
58            name: name.into(),
59            image: image.into(),
60            ..Self::default()
61        }
62    }
63}
64
65/// Compute resource constraints.
66#[derive(Debug, Clone, Default, PartialEq, Eq)]
67pub struct ResourceConfig {
68    /// CPU limit (e.g. `"0.5"`, `"1"`, `"500m"`).
69    pub cpu_limit: String,
70    /// Minimum CPU request (Kubernetes).
71    pub cpu_request: String,
72    /// Memory limit (e.g. `"512m"`, `"1g"`).
73    pub memory_limit: String,
74    /// Minimum memory request (Kubernetes).
75    pub memory_request: String,
76}
77
78/// Workload networking.
79#[derive(Debug, Clone, Default, PartialEq, Eq)]
80pub struct NetworkConfig {
81    /// Network name, `"host"`, `"bridge"`, or `"none"`.
82    pub mode: String,
83    /// Custom DNS servers.
84    pub dns: Vec<String>,
85    /// Extra `/etc/hosts` entries.
86    pub hosts: HashMap<String, String>,
87}
88
89/// Maps a workload port to a host port.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct PortMapping {
92    /// Host port.
93    pub host: u16,
94    /// Workload container port.
95    pub container: u16,
96    /// Transport protocol (`"tcp"` when empty).
97    pub protocol: String,
98}
99
100/// A storage mount.
101#[derive(Debug, Clone, Default, PartialEq, Eq)]
102pub struct VolumeMount {
103    /// Host path (Docker) or PVC/ConfigMap name (Kubernetes).
104    pub source: String,
105    /// Mount path inside the workload.
106    pub target: String,
107    /// Whether the mount is read-only.
108    pub read_only: bool,
109    /// Mount kind: `"bind"`, `"volume"`, `"configmap"`, `"secret"`, `"pvc"`
110    /// (`"bind"` when empty).
111    pub kind: String,
112}
113
114/// Controls log retrieval.
115#[derive(Debug, Clone, Default, PartialEq, Eq)]
116pub struct LogOptions {
117    /// Return only the last `tail` lines; `0` means all.
118    pub tail: usize,
119    /// Return logs no older than this duration.
120    pub since: Option<Duration>,
121    /// Stream mode (for [`crate::LogStreamer`]).
122    pub follow: bool,
123}
124
125/// Filters workloads in list operations.
126#[derive(Debug, Clone, Default, PartialEq, Eq)]
127pub struct ListFilter {
128    /// Match all labels (AND).
129    pub labels: HashMap<String, String>,
130    /// Name prefix/pattern.
131    pub name: String,
132    /// Restrict to a single state.
133    pub state: Option<WorkloadState>,
134    /// Kubernetes namespace; empty means all.
135    pub namespace: String,
136}
137
138/// Restricts which image events a [`crate::ImageEventWatcher`] returns.
139#[derive(Debug, Clone, Default, PartialEq, Eq)]
140pub struct ImageEventFilter {
141    /// Actions to match; empty means all actions.
142    pub actions: Vec<String>,
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn new_sets_name_and_image_with_defaults() {
151        let req = DeployRequest::new("api", "nginx:latest");
152        assert_eq!(req.name, "api");
153        assert_eq!(req.image, "nginx:latest");
154        assert_eq!(req.restart_policy, RestartPolicy::No);
155        assert!(req.timeout.is_none());
156        assert!(req.command.is_empty());
157    }
158
159    #[test]
160    fn deploy_request_is_cloneable_and_eq() {
161        let mut req = DeployRequest::new("api", "nginx");
162        req.labels.insert("team".into(), "core".into());
163        req.timeout = Some(Duration::from_secs(30));
164        assert_eq!(req.clone(), req);
165    }
166
167    #[test]
168    fn nested_specs_default_empty() {
169        assert_eq!(ResourceConfig::default(), ResourceConfig::default());
170        assert!(NetworkConfig::default().dns.is_empty());
171        assert!(ListFilter::default().state.is_none());
172        assert_eq!(LogOptions::default().tail, 0);
173        assert!(ImageEventFilter::default().actions.is_empty());
174    }
175}