rust_cni/libcni/
conf.rs

1// Copyright (c) 2024 https://github.com/divinerapier/cni-rs
2use log::{debug, error, trace, warn};
3use std::{
4    fs::{self, File},
5    io::{BufReader, Read},
6    path::Path,
7};
8
9use super::{
10    api::NetworkConfigList,
11    types::{NetConf, NetworkConfig},
12};
13
14pub struct ConfigFile {}
15
16impl ConfigFile {
17    pub fn config_files(dir: String, extensions: Vec<String>) -> Result<Vec<String>, String> {
18        debug!("Reading CNI config files from directory: {}", dir);
19        let mut conf_files = Vec::default();
20
21        match fs::read_dir(&dir) {
22            Ok(dir_entries) => {
23                for entry in dir_entries {
24                    match entry {
25                        Ok(file) => {
26                            let file_path = file.path();
27                            if let Some(ext) = file_path.extension() {
28                                let ext_str = ext.to_string_lossy().to_string();
29                                if extensions.contains(&ext_str) {
30                                    let path_str = file.path().to_string_lossy().to_string();
31                                    trace!("Found config file: {}", path_str);
32                                    conf_files.push(path_str);
33                                }
34                            }
35                        }
36                        Err(e) => {
37                            warn!("Error reading directory entry: {}", e);
38                        }
39                    }
40                }
41
42                debug!("Found {} config files", conf_files.len());
43                Ok(conf_files)
44            }
45            Err(e) => {
46                error!("Failed to read CNI config directory {}: {}", dir, e);
47                Err(format!("Failed to read directory {}: {}", dir, e))
48            }
49        }
50    }
51
52    pub fn config_from_bytes(datas: &[u8]) -> Result<NetworkConfigList, String> {
53        trace!("Parsing CNI config from bytes: {} bytes", datas.len());
54
55        match serde_json::from_slice::<serde_json::Value>(datas) {
56            Ok(ncmaps) => {
57                // Extract required fields
58                let name = match ncmaps.get("name") {
59                    Some(name_val) => match name_val.as_str() {
60                        Some(name_str) => name_str.to_string(),
61                        None => return Err("'name' is not a string".to_string()),
62                    },
63                    None => return Err("'name' field is required".to_string()),
64                };
65
66                let version = match ncmaps.get("cniVersion") {
67                    Some(ver_val) => match ver_val.as_str() {
68                        Some(ver_str) => ver_str.to_string(),
69                        None => return Err("'cniVersion' is not a string".to_string()),
70                    },
71                    None => return Err("'cniVersion' field is required".to_string()),
72                };
73
74                let mut disable_check = false;
75                if let Some(check) = ncmaps.get("disableCheck") {
76                    if let Some(check_bool) = check.as_bool() {
77                        disable_check = check_bool;
78                    }
79                }
80
81                let mut ncflist = NetworkConfigList::default();
82                let mut all_plugins = Vec::new();
83
84                if let Some(plugins) = ncmaps.get("plugins") {
85                    if let Some(plugins_arr) = plugins.as_array() {
86                        for plugin in plugins_arr {
87                            let string_plugin = plugin.to_string();
88                            let plg_bytes = string_plugin.as_bytes().to_vec();
89
90                            match serde_json::from_str::<NetConf>(&string_plugin) {
91                                Ok(tmp) => {
92                                    trace!("Parsed plugin: {}", tmp._type);
93                                    all_plugins.push(NetworkConfig {
94                                        network: tmp,
95                                        bytes: plg_bytes,
96                                    });
97                                }
98                                Err(e) => {
99                                    error!("Failed to parse plugin config: {}", e);
100                                    return Err(format!("Invalid plugin config: {}", e));
101                                }
102                            }
103                        }
104                    } else {
105                        return Err("'plugins' must be an array".to_string());
106                    }
107                } else {
108                    return Err("'plugins' field is required".to_string());
109                }
110
111                ncflist.name = name;
112                ncflist.cni_version = version;
113                ncflist.bytes = datas.to_vec();
114                ncflist.disable_check = disable_check;
115                ncflist.plugins = all_plugins;
116                debug!("Successfully parsed NetworkConfigList: {}", ncflist.name);
117                Ok(ncflist)
118            }
119            Err(e) => {
120                error!("Failed to parse CNI config: {}", e);
121                Err(format!("Invalid JSON: {}", e))
122            }
123        }
124    }
125
126    pub fn read_configlist_file(file_path: String) -> Option<NetworkConfigList> {
127        debug!("Reading CNI config list from file: {}", file_path);
128        let path = Path::new(&file_path);
129
130        if !path.exists() {
131            error!("Config file does not exist: {}", file_path);
132            return None;
133        }
134
135        match File::open(path) {
136            Ok(file) => {
137                let mut file_bytes = Vec::default();
138                let mut reader = BufReader::new(file);
139
140                match reader.read_to_end(&mut file_bytes) {
141                    Ok(_) => match Self::config_from_bytes(&file_bytes) {
142                        Ok(ncflist) => {
143                            debug!("Successfully read config list: {}", ncflist.name);
144                            Some(ncflist)
145                        }
146                        Err(e) => {
147                            error!("Failed to parse config list: {}", e);
148                            None
149                        }
150                    },
151                    Err(e) => {
152                        error!("Failed to read config file {}: {}", file_path, e);
153                        None
154                    }
155                }
156            }
157            Err(e) => {
158                error!("Failed to open config file {}: {}", file_path, e);
159                None
160            }
161        }
162    }
163
164    pub fn read_config_file(file_path: String) -> Option<NetworkConfig> {
165        debug!("Reading CNI single config from file: {}", file_path);
166        let path = Path::new(&file_path);
167
168        if !path.exists() {
169            error!("Config file does not exist: {}", file_path);
170            return None;
171        }
172
173        match File::open(path) {
174            Ok(file) => {
175                let mut file_bytes = Vec::default();
176                let mut reader = BufReader::new(file);
177
178                match reader.read_to_end(&mut file_bytes) {
179                    Ok(_) => match serde_json::from_slice::<NetConf>(&file_bytes) {
180                        Ok(net_conf) => {
181                            debug!("Successfully read config: {:?}", net_conf);
182                            Some(NetworkConfig {
183                                network: net_conf,
184                                bytes: file_bytes,
185                            })
186                        }
187                        Err(e) => {
188                            error!("Failed to parse config: {}", e);
189                            None
190                        }
191                    },
192                    Err(e) => {
193                        error!("Failed to read config file {}: {}", file_path, e);
194                        None
195                    }
196                }
197            }
198            Err(e) => {
199                error!("Failed to open config file {}: {}", file_path, e);
200                None
201            }
202        }
203    }
204
205    pub fn convert_to_config_list(config: NetworkConfig) -> NetworkConfigList {
206        debug!(
207            "Converting single config to config list: {}",
208            config.network.name
209        );
210        NetworkConfigList {
211            name: config.network.name.clone(),
212            cni_version: config.network.cni_version.clone(),
213            disable_check: false,
214            plugins: vec![config],
215            bytes: Vec::new(), // This will be empty for converted configs
216        }
217    }
218}