container_device_interface/
version.rs1use 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
13const 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
22const 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}