lmrc_gitlab/models/
runner.rs

1//! Runner-related data models.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6/// A GitLab CI/CD runner.
7///
8/// Runners are agents that execute CI/CD jobs. They can be shared across
9/// projects or specific to a project/group.
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct Runner {
12    /// Unique runner identifier
13    pub id: u64,
14
15    /// Runner description
16    pub description: String,
17
18    /// IP address of the runner
19    #[serde(default)]
20    pub ip_address: Option<String>,
21
22    /// Whether the runner is active
23    pub active: bool,
24
25    /// Whether the runner is currently online
26    #[serde(default)]
27    pub online: bool,
28
29    /// Current status of the runner
30    pub status: RunnerStatus,
31
32    /// Type of runner (instance, group, or project)
33    pub runner_type: RunnerType,
34
35    /// Whether the runner is shared across projects
36    #[serde(default)]
37    pub is_shared: bool,
38
39    /// Runner name
40    #[serde(default)]
41    pub name: Option<String>,
42
43    /// Tags associated with the runner
44    #[serde(default)]
45    pub tag_list: Vec<String>,
46
47    /// Version of GitLab Runner software
48    #[serde(default)]
49    pub version: Option<String>,
50
51    /// Runner architecture (e.g., "amd64", "arm64")
52    #[serde(default)]
53    pub architecture: Option<String>,
54
55    /// Runner platform (e.g., "linux", "windows", "darwin")
56    #[serde(default)]
57    pub platform: Option<String>,
58
59    /// Last time the runner contacted GitLab
60    #[serde(default)]
61    pub contacted_at: Option<DateTime<Utc>>,
62
63    /// Maximum timeout for jobs on this runner
64    #[serde(default)]
65    pub maximum_timeout: Option<u64>,
66
67    /// Whether jobs can run without tags
68    #[serde(default)]
69    pub run_untagged: bool,
70
71    /// Whether the runner is locked to specific projects
72    #[serde(default)]
73    pub locked: bool,
74
75    /// Access level (not_protected or ref_protected)
76    #[serde(default)]
77    pub access_level: Option<String>,
78}
79
80/// Status of a runner.
81///
82/// Indicates whether a runner is available to execute jobs.
83#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
84#[serde(rename_all = "snake_case")]
85pub enum RunnerStatus {
86    /// Runner is online and available
87    Online,
88
89    /// Runner is offline or unreachable
90    Offline,
91
92    /// Runner is paused and won't accept new jobs
93    Paused,
94
95    /// Runner status is not determined
96    #[serde(other)]
97    Unknown,
98}
99
100impl RunnerStatus {
101    /// Returns `true` if the runner is available to execute jobs.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use lmrc_gitlab::models::RunnerStatus;
107    ///
108    /// assert!(RunnerStatus::Online.is_available());
109    /// assert!(!RunnerStatus::Offline.is_available());
110    /// assert!(!RunnerStatus::Paused.is_available());
111    /// ```
112    pub fn is_available(self) -> bool {
113        self == Self::Online
114    }
115}
116
117impl std::fmt::Display for RunnerStatus {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            Self::Online => write!(f, "online"),
121            Self::Offline => write!(f, "offline"),
122            Self::Paused => write!(f, "paused"),
123            Self::Unknown => write!(f, "unknown"),
124        }
125    }
126}
127
128/// Type of runner.
129///
130/// Runners can be scoped to different levels of GitLab's hierarchy.
131#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
132#[serde(rename_all = "snake_case")]
133pub enum RunnerType {
134    /// Instance-level runner (available to all projects)
135    #[serde(rename = "instance_type")]
136    Instance,
137
138    /// Group-level runner (available to all projects in a group)
139    #[serde(rename = "group_type")]
140    Group,
141
142    /// Project-level runner (specific to one project)
143    #[serde(rename = "project_type")]
144    Project,
145}
146
147impl std::fmt::Display for RunnerType {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        match self {
150            Self::Instance => write!(f, "instance"),
151            Self::Group => write!(f, "group"),
152            Self::Project => write!(f, "project"),
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_runner_status_is_available() {
163        assert!(RunnerStatus::Online.is_available());
164        assert!(!RunnerStatus::Offline.is_available());
165        assert!(!RunnerStatus::Paused.is_available());
166    }
167
168    #[test]
169    fn test_runner_status_display() {
170        assert_eq!(RunnerStatus::Online.to_string(), "online");
171        assert_eq!(RunnerStatus::Offline.to_string(), "offline");
172        assert_eq!(RunnerStatus::Paused.to_string(), "paused");
173    }
174
175    #[test]
176    fn test_runner_type_display() {
177        assert_eq!(RunnerType::Instance.to_string(), "instance");
178        assert_eq!(RunnerType::Group.to_string(), "group");
179        assert_eq!(RunnerType::Project.to_string(), "project");
180    }
181
182    #[test]
183    fn test_runner_status_serialization() {
184        let status = RunnerStatus::Online;
185        let json = serde_json::to_string(&status).unwrap();
186        assert_eq!(json, "\"online\"");
187
188        let deserialized: RunnerStatus = serde_json::from_str(&json).unwrap();
189        assert_eq!(deserialized, RunnerStatus::Online);
190    }
191
192    #[test]
193    fn test_runner_type_serialization() {
194        let runner_type = RunnerType::Instance;
195        let json = serde_json::to_string(&runner_type).unwrap();
196        assert_eq!(json, "\"instance_type\"");
197
198        let deserialized: RunnerType = serde_json::from_str(&json).unwrap();
199        assert_eq!(deserialized, RunnerType::Instance);
200    }
201}