container_device_interface/
spec.rs1use std::{collections::BTreeMap, fs::File, path::PathBuf};
2
3use anyhow::{anyhow, Context, Result};
4use oci_spec::runtime as oci;
5use path_clean::clean;
6
7use crate::{
8 container_edits::ContainerEdits,
9 container_edits::Validate,
10 device::new_device,
11 device::Device,
12 internal::validation::validate::validate_spec_annotations,
13 parser::parse_qualifier,
14 parser::validate_class_name,
15 parser::validate_vendor_name,
16 specs::config::Spec as CDISpec,
17 utils::is_cdi_spec,
18 version::{minimum_required_version, VersionWrapper, VALID_SPEC_VERSIONS},
19};
20
21const DEFAULT_SPEC_EXT_SUFFIX: &str = ".yaml";
22
23#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
30pub struct Spec {
31 pub cdi_spec: CDISpec,
32 vendor: String,
33 class: String,
34 path: String,
35 priority: i32,
36 pub devices: BTreeMap<String, Device>,
37}
38
39impl Spec {
40 pub fn get_vendor(&self) -> String {
42 self.vendor.clone()
43 }
44
45 pub fn get_class(&self) -> String {
47 self.class.clone()
48 }
49
50 pub fn get_devices(&self) -> BTreeMap<String, Device> {
52 self.devices.clone()
53 }
54
55 pub fn get_device(&self, key: &str) -> Option<&Device> {
57 self.devices.get(key)
58 }
59
60 pub fn get_path(&self) -> String {
62 self.path.clone()
63 }
64
65 pub fn get_priority(&self) -> i32 {
67 self.priority
68 }
69
70 pub fn edits(&mut self) -> Option<ContainerEdits> {
72 self.cdi_spec
73 .container_edits
74 .clone()
75 .map(|ce| ContainerEdits {
76 container_edits: ce,
77 })
78 }
79
80 pub fn validate(&mut self) -> Result<BTreeMap<String, Device>> {
82 validate_version(&self.cdi_spec).context("validate cdi version failed")?;
83 validate_vendor_name(&self.vendor).context("validate vendor name failed")?;
84 validate_class_name(&self.class).context("validate class name failed")?;
85 validate_spec_annotations(&self.cdi_spec.kind, &self.cdi_spec.annotations)
86 .context("validate spec annotations failed")?;
87
88 if let Some(ref mut ce) = self.edits() {
89 ce.validate().context("validate container edits failed")?;
90 }
91
92 let mut devices = BTreeMap::new();
93 for d in &self.cdi_spec.devices {
94 let dev =
95 new_device(self, d).with_context(|| format!("failed to add device {}", d.name))?;
96 if devices.contains_key(&d.name) {
97 return Err(anyhow::anyhow!("invalid spec, multiple device {}", d.name));
98 }
99 devices.insert(d.name.clone(), dev);
100 }
101
102 Ok(devices)
103 }
104
105 pub fn apply_edits(&mut self, oci_spec: &mut oci::Spec) -> Result<()> {
107 if let Some(ref mut ce) = self.edits() {
108 ce.apply(oci_spec)
109 .context("container edits applys failed.")?;
110 }
111
112 Ok(())
113 }
114}
115
116pub fn parse_spec(path: &PathBuf) -> Result<CDISpec> {
117 if !path.exists() {
118 return Err(anyhow!("CDI spec path not found"));
119 }
120
121 let config_file = File::open(path).context("open config file")?;
122 let cdi_spec: CDISpec =
123 serde_yaml::from_reader(config_file).context("serde yaml read from file")?;
124
125 Ok(cdi_spec)
126}
127
128pub fn validate_spec(_raw_spec: &CDISpec) -> Result<()> {
130 Ok(())
132}
133
134pub fn read_spec(path: &PathBuf, priority: i32) -> Result<Spec> {
138 let raw_spec = parse_spec(path).context("parse spec file failed")?;
139 let cdi_spec = new_spec(&raw_spec, path, priority).context("create a new cdi spec failed")?;
140
141 Ok(cdi_spec)
142}
143
144pub fn new_spec(raw_spec: &CDISpec, path: &PathBuf, priority: i32) -> Result<Spec> {
148 validate_spec(raw_spec).context("invalid CDI Spec")?;
149
150 let mut cleaned_path = clean(path);
151 if !is_cdi_spec(&cleaned_path) {
152 cleaned_path.set_extension(DEFAULT_SPEC_EXT_SUFFIX);
153 }
154
155 let (vendor, class) = parse_qualifier(&raw_spec.kind);
156
157 let mut spec: Spec = Spec {
158 cdi_spec: raw_spec.clone(),
159 path: cleaned_path.display().to_string(),
160 priority,
161 vendor: vendor.to_owned(),
162 class: class.to_owned(),
163 ..Default::default()
164 };
165 spec.devices = spec.validate().context("validate spec failed")?;
166
167 Ok(spec)
168}
169
170fn validate_version(cdi_spec: &CDISpec) -> Result<()> {
171 let version = &cdi_spec.version;
172 if !VALID_SPEC_VERSIONS.is_valid_version(version) {
173 return Err(anyhow::anyhow!("invalid version {}", version));
174 }
175
176 let min_version = minimum_required_version(cdi_spec)
177 .with_context(|| "could not determine minimum required version")?;
178
179 if min_version.is_greater_than(&VersionWrapper::new(version)) {
180 return Err(anyhow::anyhow!(
181 "the spec version must be at least v{}",
182 min_version.to_string()
183 ));
184 }
185
186 Ok(())
187}