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"` (`"bind"` when empty).
110    pub kind: String,
111}
112
113/// Controls log retrieval.
114#[derive(Debug, Clone, Default, PartialEq, Eq)]
115pub struct LogOptions {
116    /// Return only the last `tail` lines; `0` means all.
117    pub tail: usize,
118    /// Return logs no older than this duration.
119    pub since: Option<Duration>,
120    /// Stream mode (for [`crate::LogStreamer`]).
121    pub follow: bool,
122}
123
124/// Filters workloads in list operations.
125#[derive(Debug, Clone, Default, PartialEq, Eq)]
126pub struct ListFilter {
127    /// Match all labels (AND).
128    pub labels: HashMap<String, String>,
129    /// Name prefix/pattern.
130    pub name: String,
131    /// Restrict to a single state.
132    pub state: Option<WorkloadState>,
133    /// Kubernetes namespace; empty means all.
134    pub namespace: String,
135}
136
137/// Restricts which image events a [`crate::ImageEventWatcher`] returns.
138#[derive(Debug, Clone, Default, PartialEq, Eq)]
139pub struct ImageEventFilter {
140    /// Actions to match; empty means all actions.
141    pub actions: Vec<String>,
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn new_sets_name_and_image_with_defaults() {
150        let req = DeployRequest::new("api", "nginx:latest");
151        assert_eq!(req.name, "api");
152        assert_eq!(req.image, "nginx:latest");
153        assert_eq!(req.restart_policy, RestartPolicy::No);
154        assert!(req.timeout.is_none());
155        assert!(req.command.is_empty());
156    }
157
158    #[test]
159    fn deploy_request_is_cloneable_and_eq() {
160        let mut req = DeployRequest::new("api", "nginx");
161        req.labels.insert("team".into(), "core".into());
162        req.timeout = Some(Duration::from_secs(30));
163        assert_eq!(req.clone(), req);
164    }
165
166    #[test]
167    fn nested_specs_default_empty() {
168        assert_eq!(ResourceConfig::default(), ResourceConfig::default());
169        assert!(NetworkConfig::default().dns.is_empty());
170        assert!(ListFilter::default().state.is_none());
171        assert_eq!(LogOptions::default().tail, 0);
172        assert!(ImageEventFilter::default().actions.is_empty());
173    }
174}