Skip to main content

atlas_local/models/
labels.rs

1use bollard::secret::ContainerInspectResponse;
2use semver::Version;
3
4use crate::models::{MongodbType, ParseMongodbTypeError};
5
6pub const LOCAL_DEPLOYMENT_LABEL_KEY: &str = "mongodb-atlas-local";
7pub const LOCAL_DEPLOYMENT_LABEL_VALUE: &str = "container";
8
9pub const MONGODB_TYPE_LABEL_KEY: &str = "mongodb-type";
10pub const MONGODB_VERSION_LABEL_KEY: &str = "version";
11
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct LocalDeploymentLabels {
15    pub mongodb_version: Version,
16    pub mongodb_type: MongodbType,
17}
18
19#[derive(Debug, thiserror::Error, PartialEq, Eq)]
20pub enum GetLocalDeploymentLabelsError {
21    #[error("Missing container labels")]
22    MissingContainerLabels,
23    #[error("Not a local deployment")]
24    NotALocalDeployment,
25    #[error("Missing mongodb version")]
26    MissingMongodbVersion,
27    #[error("Invalid mongodb version: {reason}")]
28    InvalidMongodbVersion { reason: String },
29    #[error("Missing mongodb type")]
30    MissingMongodbType,
31    #[error(transparent)]
32    InvalidMongodbType(#[from] ParseMongodbTypeError),
33}
34
35// We're implementing From<T: Borrow<ContainerInspectResponse>> for EnvironmentVariables instead of From<ContainerInspectResponse>
36// to allow using both ContainerInspectResponse and &ContainerInspectResponse, we only need a ref to the container inspect response
37impl TryFrom<&ContainerInspectResponse> for LocalDeploymentLabels {
38    type Error = GetLocalDeploymentLabelsError;
39
40    fn try_from(value: &ContainerInspectResponse) -> Result<Self, Self::Error> {
41        // Get the container labels,
42        // Every local deployment has these, we should return an error if they are not set
43        let container_config = value
44            .config
45            .as_ref()
46            .ok_or(GetLocalDeploymentLabelsError::MissingContainerLabels)?
47            .labels
48            .as_ref()
49            .ok_or(GetLocalDeploymentLabelsError::MissingContainerLabels)?;
50
51        // Verify that the container has the mongodb-atlas-local=container label
52        let atlas_local_marker_label = container_config
53            .get(LOCAL_DEPLOYMENT_LABEL_KEY)
54            .ok_or(GetLocalDeploymentLabelsError::NotALocalDeployment)?;
55        if atlas_local_marker_label != LOCAL_DEPLOYMENT_LABEL_VALUE {
56            return Err(GetLocalDeploymentLabelsError::NotALocalDeployment);
57        }
58
59        // Get the mongodb version (semver)
60        let mongodb_version_string = container_config
61            .get(MONGODB_VERSION_LABEL_KEY)
62            .ok_or(GetLocalDeploymentLabelsError::MissingMongodbVersion)?;
63        let mongodb_version = mongodb_version_string.parse::<Version>().map_err(|e| {
64            GetLocalDeploymentLabelsError::InvalidMongodbVersion {
65                reason: e.to_string(),
66            }
67        })?;
68
69        // Get the mongodb type (community or enterprise)
70        let mongodb_type_string = container_config
71            .get(MONGODB_TYPE_LABEL_KEY)
72            .ok_or(GetLocalDeploymentLabelsError::MissingMongodbType)?;
73        let mongodb_type: MongodbType = mongodb_type_string.parse()?;
74
75        Ok(LocalDeploymentLabels {
76            mongodb_version,
77            mongodb_type,
78        })
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use bollard::secret::ContainerConfig;
85
86    use super::*;
87
88    #[test]
89    fn missing_container_config() {
90        let container_inspect_response = ContainerInspectResponse::default();
91        let result = LocalDeploymentLabels::try_from(&container_inspect_response);
92        assert_eq!(
93            result,
94            Err(GetLocalDeploymentLabelsError::MissingContainerLabels)
95        );
96    }
97
98    #[test]
99    fn missing_container_labels() {
100        let container_inspect_response = ContainerInspectResponse {
101            config: Some(ContainerConfig {
102                labels: None,
103                ..Default::default()
104            }),
105            ..Default::default()
106        };
107        let result = LocalDeploymentLabels::try_from(&container_inspect_response);
108        assert_eq!(
109            result,
110            Err(GetLocalDeploymentLabelsError::MissingContainerLabels)
111        );
112    }
113
114    #[test]
115    fn missing_marker_label() {
116        use std::collections::HashMap;
117
118        let mut labels = HashMap::new();
119        labels.insert("some-other-label".to_string(), "value".to_string());
120
121        let container_inspect_response = ContainerInspectResponse {
122            config: Some(ContainerConfig {
123                labels: Some(labels),
124                ..Default::default()
125            }),
126            ..Default::default()
127        };
128        let result = LocalDeploymentLabels::try_from(&container_inspect_response);
129        assert_eq!(
130            result,
131            Err(GetLocalDeploymentLabelsError::NotALocalDeployment)
132        );
133    }
134
135    #[test]
136    fn invalid_marker_label_value() {
137        use std::collections::HashMap;
138
139        let mut labels = HashMap::new();
140        labels.insert(
141            LOCAL_DEPLOYMENT_LABEL_KEY.to_string(),
142            "wrong-value".to_string(),
143        );
144
145        let container_inspect_response = ContainerInspectResponse {
146            config: Some(ContainerConfig {
147                labels: Some(labels),
148                ..Default::default()
149            }),
150            ..Default::default()
151        };
152        let result = LocalDeploymentLabels::try_from(&container_inspect_response);
153        assert_eq!(
154            result,
155            Err(GetLocalDeploymentLabelsError::NotALocalDeployment)
156        );
157    }
158
159    #[test]
160    fn missing_mongodb_version() {
161        use std::collections::HashMap;
162
163        let mut labels = HashMap::new();
164        labels.insert(
165            LOCAL_DEPLOYMENT_LABEL_KEY.to_string(),
166            LOCAL_DEPLOYMENT_LABEL_VALUE.to_string(),
167        );
168
169        let container_inspect_response = ContainerInspectResponse {
170            config: Some(ContainerConfig {
171                labels: Some(labels),
172                ..Default::default()
173            }),
174            ..Default::default()
175        };
176        let result = LocalDeploymentLabels::try_from(&container_inspect_response);
177        assert_eq!(
178            result,
179            Err(GetLocalDeploymentLabelsError::MissingMongodbVersion)
180        );
181    }
182
183    #[test]
184    fn invalid_mongodb_version() {
185        use std::collections::HashMap;
186
187        let mut labels = HashMap::new();
188        labels.insert(
189            LOCAL_DEPLOYMENT_LABEL_KEY.to_string(),
190            LOCAL_DEPLOYMENT_LABEL_VALUE.to_string(),
191        );
192        labels.insert(
193            MONGODB_VERSION_LABEL_KEY.to_string(),
194            "not-a-semver".to_string(),
195        );
196
197        let container_inspect_response = ContainerInspectResponse {
198            config: Some(ContainerConfig {
199                labels: Some(labels),
200                ..Default::default()
201            }),
202            ..Default::default()
203        };
204        let result = LocalDeploymentLabels::try_from(&container_inspect_response);
205        assert!(matches!(
206            result,
207            Err(GetLocalDeploymentLabelsError::InvalidMongodbVersion { .. })
208        ));
209    }
210
211    #[test]
212    fn missing_mongodb_type() {
213        use std::collections::HashMap;
214
215        let mut labels = HashMap::new();
216        labels.insert(
217            LOCAL_DEPLOYMENT_LABEL_KEY.to_string(),
218            LOCAL_DEPLOYMENT_LABEL_VALUE.to_string(),
219        );
220        labels.insert(MONGODB_VERSION_LABEL_KEY.to_string(), "7.0.0".to_string());
221
222        let container_inspect_response = ContainerInspectResponse {
223            config: Some(ContainerConfig {
224                labels: Some(labels),
225                ..Default::default()
226            }),
227            ..Default::default()
228        };
229        let result = LocalDeploymentLabels::try_from(&container_inspect_response);
230        assert_eq!(
231            result,
232            Err(GetLocalDeploymentLabelsError::MissingMongodbType)
233        );
234    }
235
236    #[test]
237    fn invalid_mongodb_type() {
238        use std::collections::HashMap;
239
240        let mut labels = HashMap::new();
241        labels.insert(
242            LOCAL_DEPLOYMENT_LABEL_KEY.to_string(),
243            LOCAL_DEPLOYMENT_LABEL_VALUE.to_string(),
244        );
245        labels.insert(MONGODB_VERSION_LABEL_KEY.to_string(), "7.0.0".to_string());
246        labels.insert(
247            MONGODB_TYPE_LABEL_KEY.to_string(),
248            "invalid-type".to_string(),
249        );
250
251        let container_inspect_response = ContainerInspectResponse {
252            config: Some(ContainerConfig {
253                labels: Some(labels),
254                ..Default::default()
255            }),
256            ..Default::default()
257        };
258        let result = LocalDeploymentLabels::try_from(&container_inspect_response);
259        assert!(matches!(
260            result,
261            Err(GetLocalDeploymentLabelsError::InvalidMongodbType(_))
262        ));
263    }
264
265    #[test]
266    fn successful_parse() {
267        use std::collections::HashMap;
268
269        let mut labels = HashMap::new();
270        labels.insert(
271            LOCAL_DEPLOYMENT_LABEL_KEY.to_string(),
272            LOCAL_DEPLOYMENT_LABEL_VALUE.to_string(),
273        );
274        labels.insert(MONGODB_VERSION_LABEL_KEY.to_string(), "7.0.0".to_string());
275        labels.insert(MONGODB_TYPE_LABEL_KEY.to_string(), "community".to_string());
276
277        let container_inspect_response = ContainerInspectResponse {
278            config: Some(ContainerConfig {
279                labels: Some(labels),
280                ..Default::default()
281            }),
282            ..Default::default()
283        };
284        let result = LocalDeploymentLabels::try_from(&container_inspect_response);
285
286        assert!(result.is_ok());
287        let labels = result.unwrap();
288        assert_eq!(labels.mongodb_version, Version::parse("7.0.0").unwrap());
289        assert_eq!(labels.mongodb_type, MongodbType::Community);
290    }
291}