Skip to main content

opencode_cloud_core/docker/
version.rs

1//! Docker image version detection
2//!
3//! Reads version information from Docker image labels.
4
5use super::{DockerClient, DockerError};
6
7/// Version label key in Docker image
8pub const VERSION_LABEL: &str = "org.opencode-cloud.version";
9
10/// Get version from image label
11///
12/// Returns None if image doesn't exist or has no version label.
13/// Version label is set during automated builds; local builds have "dev".
14pub async fn get_image_version(
15    client: &DockerClient,
16    image_name: &str,
17) -> Result<Option<String>, DockerError> {
18    let inspect = match client.inner().inspect_image(image_name).await {
19        Ok(info) => info,
20        Err(bollard::errors::Error::DockerResponseServerError {
21            status_code: 404, ..
22        }) => {
23            return Ok(None);
24        }
25        Err(e) => {
26            return Err(DockerError::Connection(format!(
27                "Failed to inspect image: {e}"
28            )));
29        }
30    };
31
32    // Extract version from labels
33    let version = inspect
34        .config
35        .and_then(|c| c.labels)
36        .and_then(|labels| labels.get(VERSION_LABEL).cloned());
37
38    Ok(version)
39}
40
41/// CLI version from Cargo.toml
42pub fn get_cli_version() -> &'static str {
43    env!("CARGO_PKG_VERSION")
44}
45
46/// Compare versions and determine if they match
47///
48/// Returns true if versions are compatible (same or dev build).
49/// Returns false if versions differ and user should be prompted.
50pub fn versions_compatible(cli_version: &str, image_version: Option<&str>) -> bool {
51    match image_version {
52        None => true,        // No version label = local build, assume compatible
53        Some("dev") => true, // Dev build, assume compatible
54        Some(img_ver) => cli_version == img_ver,
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_versions_compatible_none() {
64        assert!(versions_compatible("1.0.8", None));
65    }
66
67    #[test]
68    fn test_versions_compatible_dev() {
69        assert!(versions_compatible("1.0.8", Some("dev")));
70    }
71
72    #[test]
73    fn test_versions_compatible_same() {
74        assert!(versions_compatible("1.0.8", Some("1.0.8")));
75    }
76
77    #[test]
78    fn test_versions_compatible_different() {
79        assert!(!versions_compatible("1.0.8", Some("1.0.7")));
80    }
81
82    #[test]
83    fn test_get_cli_version_format() {
84        let version = get_cli_version();
85        // Should be semver format
86        assert!(version.contains('.'));
87        let parts: Vec<&str> = version.split('.').collect();
88        assert_eq!(parts.len(), 3);
89    }
90
91    #[test]
92    fn test_version_label_constant() {
93        assert_eq!(VERSION_LABEL, "org.opencode-cloud.version");
94    }
95}