container_device_interface/
version.rs

1use anyhow::Result;
2use once_cell::sync::Lazy;
3use std::collections::BTreeMap;
4
5use crate::parser::parse_qualifier;
6use crate::specs::config;
7use crate::specs::config::Spec as CDISpec;
8use semver::Version;
9
10const CURRENT_VERSION: &str = config::CURRENT_VERSION;
11static VCURRENT: Lazy<String> = Lazy::new(|| format!("v{}", CURRENT_VERSION));
12
13// Released versions of the CDI specification
14const V010: &str = "v0.1.0";
15const V020: &str = "v0.2.0";
16const V030: &str = "v0.3.0";
17const V040: &str = "v0.4.0";
18const V050: &str = "v0.5.0";
19const V060: &str = "v0.6.0";
20const V070: &str = "v0.7.0";
21
22// Earliest supported version of the CDI specification
23const VEARLIEST: &str = V030;
24
25type RequiredFunc = fn(&CDISpec) -> bool;
26
27#[derive(Default)]
28pub struct VersionMap(BTreeMap<String, Option<RequiredFunc>>);
29
30pub static VALID_SPEC_VERSIONS: Lazy<VersionMap> = Lazy::new(|| {
31    let mut map = BTreeMap::new();
32    map.insert(V010.to_string(), None);
33    map.insert(V020.to_string(), None);
34    map.insert(V030.to_string(), None);
35    map.insert(V040.to_string(), Some(requires_v040 as RequiredFunc));
36    map.insert(V050.to_string(), Some(requires_v050));
37    map.insert(V060.to_string(), Some(requires_v060));
38    map.insert(V070.to_string(), Some(requires_v070));
39    VersionMap(map)
40});
41
42impl VersionMap {
43    pub fn is_valid_version(&self, spec_version: &str) -> bool {
44        self.0
45            .contains_key(&format!("v{}", VersionWrapper::new(spec_version)))
46    }
47
48    pub fn required_version(&self, spec: &CDISpec) -> VersionWrapper {
49        let mut min_version = VersionWrapper::new(VEARLIEST);
50        for (v, is_required) in &self.0 {
51            if let Some(is_required_fn) = is_required {
52                let version_wrapper = VersionWrapper::new(v);
53                if is_required_fn(spec) && version_wrapper.is_greater_than(&min_version) {
54                    min_version = version_wrapper;
55                }
56                if min_version.is_latest() {
57                    break;
58                }
59            }
60        }
61        min_version
62    }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
66pub struct VersionWrapper(String);
67
68impl std::fmt::Display for VersionWrapper {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        write!(f, "{}", self.0.trim_start_matches('v'))
71    }
72}
73
74impl VersionWrapper {
75    pub fn new(v: &str) -> Self {
76        VersionWrapper(format!("v{}", v.trim_start_matches('v')))
77    }
78
79    pub fn is_greater_than(&self, other: &VersionWrapper) -> bool {
80        Version::parse(&self.to_string()).unwrap() > Version::parse(&other.to_string()).unwrap()
81    }
82
83    fn is_latest(&self) -> bool {
84        self.0 == *VCURRENT
85    }
86}
87
88pub fn minimum_required_version(spec: &CDISpec) -> Result<VersionWrapper> {
89    Ok(VALID_SPEC_VERSIONS.required_version(spec))
90}
91
92fn requires_v070(spec: &CDISpec) -> bool {
93    let edits = &spec.container_edits;
94    if let Some(edits) = edits {
95        if edits.intel_rdt.as_ref().is_some() {
96            return true;
97        }
98        if edits
99            .additional_gids
100            .as_ref()
101            .is_some_and(|v| !v.is_empty())
102        {
103            return true;
104        }
105    }
106
107    for d in &spec.devices {
108        let edits = &d.container_edits;
109
110        if edits.intel_rdt.as_ref().is_some() {
111            return true;
112        }
113        if edits
114            .additional_gids
115            .as_ref()
116            .is_some_and(|v| !v.is_empty())
117        {
118            return true;
119        }
120    }
121    false
122}
123
124fn requires_v060(spec: &CDISpec) -> bool {
125    if !spec.annotations.is_empty() {
126        return true;
127    }
128    for d in &spec.devices {
129        if !d.annotations.is_empty() {
130            return true;
131        }
132    }
133    let (vendor, class) = parse_qualifier(&spec.kind);
134    if !vendor.is_empty() && class.contains('.') {
135        return true;
136    }
137    false
138}
139
140fn requires_v050(spec: &CDISpec) -> bool {
141    if spec
142        .devices
143        .iter()
144        .any(|d| !d.name.chars().next().unwrap_or_default().is_alphabetic())
145    {
146        return true;
147    }
148
149    let edits = spec
150        .devices
151        .iter()
152        .map(|d| &d.container_edits)
153        .chain(spec.container_edits.as_ref());
154
155    edits
156        .flat_map(|edits| edits.device_nodes.iter().flat_map(|nodes| nodes.iter()))
157        .any(|node| {
158            node.host_path
159                .as_deref()
160                .is_some_and(|path| !path.is_empty())
161        })
162}
163
164fn requires_v040(spec: &CDISpec) -> bool {
165    spec.devices
166        .iter()
167        .map(|d| &d.container_edits)
168        .chain(spec.container_edits.as_ref())
169        .flat_map(|edits| edits.mounts.iter().flat_map(|mounts| mounts.iter()))
170        .any(|mount| mount.r#type.as_ref().is_some_and(|typ| !typ.is_empty()))
171}